youcongtech

youcongtech 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织 www.cnblogs.com/youcong/ 编辑
编辑

我不仅仅喜欢编程,而且还喜欢读书和旅行还有运动。
世界那么大,我想去走走。见见未曾见过的事与物,在异地/异国享受不一样的风土人情,或是美景。

个人微信公众号:ChallengerTech
不定期分享个人知识领域和心得、经历等。

个人动态

youcongtech 发布了文章 · 11月15日

SpringBoot集成Shiro

任何一个企业级系统,权限必不可少

早年写的关于shiro(基本上是基于SSM框架(即Spring+SpringMVC+MyBatis)文章如下(仅供参考):
shiro实战系列\(一\)之入门实战
Spring\(二\)之入门示例
shiro实战系列\(二\)之入门实战续
shiro实战系列\(三\)之架构
shiro实战系列\(四\)之配置
shiro实战系列\(五\)之Authentication\(身份验证\)
shiro实战系列\(六\)之Authorization\(授权\)
shiro实战系列\(七\)之Realm
shiro实战系列\(八\)之安全管理器
shiro实战系列\(九\)之Web
shiro实战系列\(十\)之Subject
shiro实战系列\(十一\)之Caching
shiro实战系列\(十二\)之常用专业术语
shiro实战系列\(十三\)之单元测试
shiro实战系列\(十四\)之配置
shiro实战系列\(十五\)之Spring集成Shiro

上面一共十五篇文章是早年在创业公司做相关的技术调研整理而成的,代码例子较少,偏理论性比较强,所以本篇文章不再赘述一些理论性内容,接下来开始进入实战。

一、导入Maven依赖

这里列举的是子模块pom.xml

<properties>
    <java.version>1.8</java.version>
    <druid-spring-boot-starter.version>1.1.13</druid-spring-boot-starter.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-extension</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid-spring-boot-starter.version}</version>
    </dependency>
    <!-- SpringBoot Web -->
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- AOP依赖,一定要加,否则权限拦截验证不生效 -->
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- Mysql Connector -->
 <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <!-- Redis -->
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <!-- Shiro 核心依赖 -->
 <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    <!-- Shiro-redis插件 -->
 <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>3.1.0</version>
    </dependency>
    <!-- StringUtilS工具 -->
 <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.5</version>
    </dependency>
    <!-- json 转换工具 -->
 <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

父pom.xml(主要针对SpringBoot版本):

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/>
    </parent>

版本一定要对,否则会有各种奇葩问题。

例如(如果版本不对会出现这样的问题,启动正常,在请求登录接口就会报这样的错误):
错误信息:

java.lang.NoSuchMethodError: redis.clients.jedis.ScanResult.getStringCursor()...

二、配置(application.yml)

server:
  port: 5050
spring:
  # Redis数据源
 redis:
    host: localhost
    port: 6379
 timeout: 6000
    password: 123456
    jedis:
      pool:
        max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
 max-wait: -1      # 连接池最大阻塞等待时间(使用负值表示没有限制)
 max-idle: 10 # 连接池中的最大空闲连接
 min-idle: 5 # 连接池中的最小空闲连接
 # 配置数据源 datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/wordpress?useUnicode=true&characterEncoding=utf-8&serverTimeZone=GMT
    username: root
    password: 1234
    type: com.alibaba.druid.pool.DruidDataSource
# mybatis-plus相关配置
mybatis-plus:
  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
 mapper-locations: classpath:mapper/*.xml
  # 以下配置均有默认值,可以不设置
 global-config:
    db-config:
      #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
 id-type: auto
 #字段策略 IGNORED:"忽略判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"
 field-strategy: NOT_EMPTY
      #数据库类型
 db-type: MYSQL
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
 map-underscore-to-camel-case: true
 # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
 call-setters-on-nulls: true
 # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

三、编写Shiro核心配置类

核心配置类特别注意的是接口放行,否则访问接口会出现404。

package com.blog.tutorial07.shiro.config;
import com.blog.tutorial07.shiro.shiro.ShiroRealm;
import com.blog.tutorial07.shiro.shiro.ShiroSessionIdGenerator;
import com.blog.tutorial07.shiro.shiro.ShiroSessionManager;
import com.blog.tutorial07.shiro.utils.SHA256Util;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * @Description Shiro配置类
 * @Author youcong
 */@Configuration
public class ShiroConfig {
    private final String CACHE_KEY = "shiro:cache:";
    private final String SESSION_KEY = "shiro:session:";
    private final int EXPIRE = 1800;
    //Redis配置
 @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.password}")
    private String password;
    /**
 * 开启Shiro-aop注解支持 * @Attention 使用代理方式所以需要开启代码支持
 * @Author youcong
 */ @Bean
 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
 * Shiro基础配置 * @Author youcong
 */ @Bean
 public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 注意过滤器配置顺序不能颠倒
 // 配置过滤:不会被拦截的链接 filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/user/**", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        // 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
 shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    /**
 * 安全管理器 * @Author youcong
 */ @Bean
 public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义Ssession管理
 securityManager.setSessionManager(sessionManager());
        // 自定义Cache实现
 securityManager.setCacheManager(cacheManager());
        // 自定义Realm验证
 securityManager.setRealm(shiroRealm());
        return securityManager;
    }
    /**
 * 身份验证器 * @Author youcong
 */ @Bean
 public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }
    /**
 * 凭证匹配器 * 将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置 * @Author youcong
 */ @Bean
 public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用SHA256算法;
 shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
        // 散列的次数,比如散列两次,相当于 md5(md5(""));
 shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
        return shaCredentialsMatcher;
    }
    /**
 * 配置Redis管理器 * @Attention 使用的是shiro-redis开源插件
 * @Author youcong
 */ @Bean
 public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }
    /**
 * 配置Cache管理器 * 用于往Redis存储权限和角色标识 * @Attention 使用的是shiro-redis开源插件
 * @Author youcong
 */ @Bean
 public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix(CACHE_KEY);
        // 配置缓存的话要求放在session里面的实体类必须有个id标识
 redisCacheManager.setPrincipalIdFieldName("id");
        return redisCacheManager;
    }
    /**
 * SessionID生成器 * @Author youcong
 */ @Bean
 public ShiroSessionIdGenerator sessionIdGenerator(){
        return new ShiroSessionIdGenerator();
    }
    /**
 * 配置RedisSessionDAO * @Attention 使用的是shiro-redis开源插件
 * @Author youcong
 */ @Bean
 public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
        redisSessionDAO.setKeyPrefix(SESSION_KEY);
        redisSessionDAO.setExpire(EXPIRE);
        return redisSessionDAO;
    }
    /**
 * 配置Session管理器 * @Author youcong
 */ @Bean
 public SessionManager sessionManager() {
        ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
        shiroSessionManager.setSessionDAO(redisSessionDAO());
        return shiroSessionManager;
    }
}

四、编写核心配置类中涉及的相关类

ShiroRealm.java

package com.blog.tutorial07.shiro.shiro;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.blog.tutorial07.shiro.entity.Usermeta;
import com.blog.tutorial07.shiro.entity.Users;
import com.blog.tutorial07.shiro.service.UsermetaService;
import com.blog.tutorial07.shiro.service.UsersService;
import com.blog.tutorial07.shiro.utils.ShiroUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
 * @Description Shiro权限匹配和账号密码匹配
 * @Author Sans
 * @CreateTime 2019/6/15 11:27
 */public class ShiroRealm extends AuthorizingRealm {
    @Autowired
 private UsersService sysUserService;
    @Autowired
 private UsermetaService sysRoleService;
    /**
 * 授权权限 * 用户进行权限验证时候Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中 * * @Author Sans
 * @CreateTime 2019/6/12 11:44
 */ @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户ID
 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        Users user = (Users) principalCollection.getPrimaryPrincipal();
        Long userId = user.getId();
        //这里可以进行授权和处理
 Set<String> rolesSet = new HashSet<>();
        QueryWrapper<Usermeta> roleWrapper = new QueryWrapper<>();
        roleWrapper.eq("user_id", user.getId());
        roleWrapper.eq("meta_key", "wp_user_level");
        List<Usermeta> roleList = sysRoleService.list(roleWrapper);
        for (Usermeta role : roleList) {
            rolesSet.add(role.getMetaValue());
        }
        authorizationInfo.setRoles(rolesSet);
        return authorizationInfo;
    }
    /**
 * 身份认证 * * @Author Sans
 * @CreateTime 2019/6/12 12:36
 */ @Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取用户的输入的账号.
 String username = (String) authenticationToken.getPrincipal();
        //通过username从数据库中查找 User对象,如果找到进行验证
 //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("user_login", username);
        Users user = sysUserService.getOne(wrapper);
        //判断账号是否存在
 if (user == null) {
            throw new AuthenticationException();
        }
        //判断账号是否被冻结
 if (user.getUserStatus() == null || user.getUserStatus().equals("1")) {
            throw new LockedAccountException();
        }
        //进行验证
 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user,                                  //用户名
 user.getUserPass(),                    //密码
 ByteSource.Util.bytes(user.getUserActivationKey()), //设置盐值
 getName()
        );
        //验证成功开始踢人(清除缓存和Session)
 ShiroUtils.deleteCache(username, true);
        return authenticationInfo;
    }
}

ShiroSessionIdGenerator.java

package com.blog.tutorial07.shiro.shiro;
import com.blog.tutorial07.shiro.constant.RedisConstant;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import java.io.Serializable;
/**
 * @Description 自定义SessionId生成器
 * @Author youcong
 */public class ShiroSessionIdGenerator implements SessionIdGenerator {
    @Override
 public Serializable generateId(Session session) {
        Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
        return String.format(RedisConstant.REDIS_PREFIX_LOGIN, sessionId);
    }
}

ShiroSessionManager.java

package com.blog.tutorial07.shiro.shiro;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
 * @Description 自定义获取Token
 * @Author youcong
 */public class ShiroSessionManager extends DefaultWebSessionManager {
    //定义常量
 private static final String AUTHORIZATION = "Authorization";
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    //重写构造器
 public ShiroSessionManager() {
        super();
        this.setDeleteInvalidSessions(true);
    }
    /**
 * 重写方法实现从请求头获取Token便于接口统一 * 每次请求进来,Shiro会去从请求头找Authorization这个key对应的Value(Token) * @Author youcong
 */ @Override
 public Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中存在token 则从请求头中获取token
 if (!StringUtils.isEmpty(token)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return token;
        } else {
            // 这里禁用掉Cookie获取方式
 // 按默认规则从Cookie取Token // return super.getSessionId(request, response); return null;
        }
    }
}

五、编写相关工具类

SHA256Util.java

package com.blog.tutorial07.shiro.utils;
import org.apache.shiro.crypto.hash.SimpleHash;
/**
 * @Description Sha-256加密工具
 * @Author youcong
 */public class SHA256Util {
    /**  私有构造器 **/
 private SHA256Util(){};
    /**  加密算法 **/
 public final static String HASH_ALGORITHM_NAME = "SHA-256";
    /**  循环次数 **/
 public final static int HASH_ITERATIONS = 15;
    /**  执行加密-采用SHA256和盐值加密 **/
 public static String sha256(String password, String salt) {
        return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();
    }
}

ShiroUtils.java

package com.blog.tutorial07.shiro.utils;
import com.blog.tutorial07.shiro.entity.Users;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.LogoutAware;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisSessionDAO;
import java.util.Collection;
import java.util.Objects;
/**
 * @Description Shiro工具类
 * @Author youcong
 */public class ShiroUtils {
    /**
 * 私有构造器 **/ private ShiroUtils() {
    }
    private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
    /**
 * 获取当前用户Session * * @Author youcong
 * @Return SysUserEntity 用户信息
 */ public static Session getSession() {
        return SecurityUtils.getSubject().getSession();
    }
    /**
 * 用户登出 * * @Author youcong
 */ public static void logout() {
        SecurityUtils.getSubject().logout();
    }
    /**
 * 获取当前用户信息 * * @Author youcong
 * @Return SysUserEntity 用户信息
 */ public static Users getUserInfo() {
        return (Users) SecurityUtils.getSubject().getPrincipal();
    }
    /**
 * 删除用户缓存信息 * * @Author youcong
 * @Param username  用户名称
 * @Param isRemoveSession 是否删除Session
 * @Return void
 */ public static void deleteCache(String username, boolean isRemoveSession) {
        //从缓存中获取Session
 Session session = null;
        Collection<Session> sessions = redisSessionDAO.getActiveSessions();
        Users sysUserEntity;
        Object attribute = null;
        for (Session sessionInfo : sessions) {
            //遍历Session,找到该用户名称对应的Session
 attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (attribute == null) {
                continue;
            }
            sysUserEntity = (Users) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
            if (sysUserEntity == null) {
                continue;
            }
            if (Objects.equals(sysUserEntity.getUserLogin(), username)) {
                session = sessionInfo;
                break;
            }
        }
        if (session == null || attribute == null) {
            return;
        }
        //删除session
 if (isRemoveSession) {
            redisSessionDAO.delete(session);
        }
        //删除Cache,在访问受限接口时会重新授权
 DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
        Authenticator authc = securityManager.getAuthenticator();
        ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
    }
}

SpringUtil.java

package com.blog.tutorial07.shiro.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @Description Spring上下文工具类
 * @Author youcong
 */@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext context;
    /**
 * Spring在bean初始化后会判断是不是ApplicationContextAware的子类 * 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去 * @Author youcong
 */ @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    /**
 * 通过Name返回指定的Bean * @Author youcong
 */ public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }
}

六、编写Controller

package com.blog.tutorial07.shiro.controller;
import com.blog.tutorial07.shiro.entity.Users;
import com.blog.tutorial07.shiro.service.UsersService;
import com.blog.tutorial07.shiro.utils.ShiroUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
 private UsersService usersService;
    @Autowired
 private RedisTemplate redisTemplate;
    /**
 * 登录 * * @Author youcong
 */ @PostMapping("/login")
    public Map<String, Object> login(@RequestParam String username, @RequestParam String password) {
        Map<String, Object> map = new HashMap<>();
        //进行身份验证
 try {
            //验证身份和登陆
 Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            //进行登录操作
 subject.login(token);
        } catch (IncorrectCredentialsException e) {
            map.put("code", 500);
            map.put("msg", "用户不存在或者密码错误");
            return map;
        } catch (LockedAccountException e) {
            map.put("code", 500);
            map.put("msg", "登录失败,该用户已被冻结");
            return map;
        } catch (AuthenticationException e) {
            map.put("code", 500);
            map.put("msg", "该用户不存在");
            return map;
        } catch (Exception e) {
            map.put("code", 500);
            map.put("msg", "未知异常");
            return map;
        }
        map.put("code", 0);
        map.put("msg", "登录成功");
        map.put("token", ShiroUtils.getSession().getId().toString());
        return map;
    }
    /**
 * 未登录 * * @Author youcong
 */ @RequestMapping("/unauth")
    public Map<String, Object> unauth() {
        Map<String, Object> map = new HashMap<>();
        map.put("code", 500);
        map.put("msg", "未登录");
        return map;
    }
    @PostMapping("/list")
    @RequiresRoles("1")
    public String list() {
        System.out.println("list:" + redisTemplate.opsForValue().get("list"));
        if (StringUtils.isEmpty(redisTemplate.opsForValue().get("list"))) {
            redisTemplate.opsForValue().set("list", usersService.list(), 360, TimeUnit.MINUTES);
        }
        return redisTemplate.opsForValue().get("list").toString();
    }
}

七、测试(使用PostMan)

1.登录

image.png

查看redis,如图:
image.png

2.测试没有权限的接口

image.png

3.赋予权限再次测试

image.png

八、总结

无论是SpringSecurity还是Shiro,基本上整合非常相似,也很简单。
如果有朋友看完这篇文章还是不明白的话,可以访问如下地址:
https://github.com/developers...
将项目克隆到本地运行。这个git仓库,sql脚本什么的都有。

我本次用到的类基本上是基于这个的,只不过数据表不一样,我本次所使用的是wordpress的数据库。

查看原文

赞 13 收藏 13 评论 0

youcongtech 发布了文章 · 11月15日

SpringBoot集成Redis

一、为什么要使用Redis?

  • 性能极高 – Redis读的速度是110000次/s,写的速度是81000次/s ;
  • 丰富的数据类型 – Redis支持Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作;
  • 原子 – Redis的所有操作都是原子性的,即要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来;

*丰富的特性 – Redis还支持publish/subscribe, key过期等特性。

二、什么情况应该使用Redis?

  • 会话缓存(如分布式应用存储token等);
  • 计数器(如一天之内密码输错了三次,自动锁定账户);
  • 热点数据(如排名等);
  • 缓解数据库压力(MyBatis或MyBatis-Plus使用Redis做二级缓存);
  • 定时器(主要针对redis的key过期时间)。

三、整合Redis

1.导入Maven依赖

<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置文件application.yml修改

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password:

3.Controller测试(可复用SpringBoot整合MyBatis-Plus例子)

package com.blog.tutorial.controller;
import com.blog.tutorial.entity.Users;
import com.blog.tutorial.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
 private UsersService usersService;
    @Autowired
 private RedisTemplate redisTemplate;
    @GetMapping("/list")
    public String list() {
        System.out.println("list:"+redisTemplate.opsForValue().get("list"));
        if (StringUtils.isEmpty(redisTemplate.opsForValue().get("list"))) {
            redisTemplate.opsForValue().set("list", usersService.list(), 360, TimeUnit.MINUTES);
        }
        return redisTemplate.opsForValue().get("list").toString();
    }
}

4.测试效果

请求接口,如下:
image.png

控制台,如下:
image.png

初次请求,会打印SQL,再次请求只会输出Redis的key,同时页面接口响应时间非常快。

四、Redis常见问题有哪些?

  • 缓存和数据库双写一致性(缓存的数据与数据库查询的数据不一样);
  • 缓存雪崩(即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常);
  • 缓存击穿(黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常);
  • 缓存并发竞争(多个系统同时set同一个key,涉及分布式锁)。

五、Java的Redis框架有哪些?

  • Jedis;
  • Lettuce;
  • Redisson。

关于上述框架使用,我在我的博客园写下如下几篇文章,感兴趣的可以看看:
SpringBoot整合Redisson\(单机版\)

SpringBoot实战\(七\)之与Redis进行消息传递

SpringBoot整合Redisson\(集群版\)

redis集群搭建

Java连接Redis之redis的增删改查

网站性能优化小结和spring整合redis

查看原文

赞 1 收藏 1 评论 0

youcongtech 发布了文章 · 11月14日

MyBatis-Plus之CRUD

曾在博客园写下关于MyBatis-Plus实战相关的文章,一共二十篇,不过那个时候都是基于MyBatis-Plus2.x,近来我的博客产品,技术框架升级,随之,MyBatis2.x升级到3.x,大改了从Entity、Dao到Service以及Controller等代码。

如果有朋友还在使用MyBatis-Plus2.x版本的话,可以参考我在博客园写的一系列文章,文章链接为:
MP实战系列\(一共二十篇\)

效果图分别如下:
image.png

image.png

image.png

image.png

一、Entity

Entity又称数据模型,通常对应数据表。常用的注解如@TableName、@TableId、@TableField等,基本上没变,主要的变化是引用包路径发生改变。

二、Dao

image.png

查看了下BaseMapper,代码如下:

public interface BaseMapper<T> extends Mapper<T> {
    int insert(T entity);
    int deleteById(Serializable id);
    int deleteByMap(@Param("cm") Map<String, Object> columnMap);
    int delete(@Param("ew") Wrapper<T> queryWrapper);
    int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    int updateById(@Param("et") T entity);
    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
    T selectById(Serializable id);
    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
    T selectOne(@Param("ew") Wrapper<T> queryWrapper);
    Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
    <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
}

变化不大,其中70%是我在2.x版本用过的,如insert、deleteById、delete、updateById、update、selectById、selectByMap、selectOne、selectList、selectPage等。

要说变化的,Dao给我比较直观的感觉是过去的EntityWrapper变成了QueryWrapper。这块也是我在我的博客产品里改动最多的地方之一。

三、Service

Service这层,主要体现在Controller调用的时候。

1.增加

image.png

2.删除

image.png

3.修改

image.png

4.查询

image.png

给我比较直观的感觉,更简洁了。同时这块也是我改动最多了。

四、其它

从上面三点来看,很难看出变动大的具体是哪个,基本上都是一些API变更,过去的方法名没有了,需要修改成新的。

要看具体版本更新,还是得去官方文档看版本更新日志(我使用的是最新的3.4.1版本,这里不建议使用最新的,最新的意味着版本不稳定性,还是使用3.x比较稳定的):

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

这里就不接着列举了,之所以列举出于这么几个考虑?
第一、在升级版本之前最好了解一些将要升级的版本主要新增哪些API或者修复哪些bug以及与原有版本的兼容性;
第二、对于我们做开源项目有好处,了解他们的提交规范和从中发现哪些问题比较频繁。

五、CRUD例子

1.Service

package com.blog.tutorial06.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.blog.tutorial06.entity.Users;
import java.util.List;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:26
 */public interface UsersService extends IService<Users> {
    int add(Users user);
    int del(Long id);
    int modify(Users user);
    List<Users> selectAll();
}

2.Service实现类

package com.blog.tutorial06.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blog.tutorial06.entity.Users;
import com.blog.tutorial06.dao.UsersDao;
import com.blog.tutorial06.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@Service
public class UsersServiceImpl extends ServiceImpl<UsersDao, Users> implements UsersService {
    @Autowired
 private UsersDao usersDao;
    @Override
 public int add(Users user) {
        return usersDao.insert(user);
    }
    @Override
 public int del(Long id) {
        return usersDao.deleteById(id);
    }
    @Override
 public int modify(Users user) {
        return usersDao.updateById(user);
    }
    @Override
 public List<Users> selectAll() {
        return usersDao.selectList(null);
    }
}

3.Controller

package com.blog.tutorial06.controller;
import com.blog.tutorial06.entity.Users;
import com.blog.tutorial06.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
 private UsersService usersService;
    @PostMapping("/save")
    public int save(Users user) {
        return usersService.add(user);
    }
    @DeleteMapping("/del")
    public int del(Long id) {
        return usersService.del(id);
    }
    @PutMapping("/modify")
    public int modify(Users user) {
        return usersService.modify(user);
    }
    @GetMapping("/list")
    public List<Users> list() {
        return usersService.selectAll();
    }
}

4.这里就不列举测试了,和前面类似

查看原文

赞 0 收藏 0 评论 0

youcongtech 发布了文章 · 11月14日

MyBatis-Plus之逻辑删除

特别是互联网项目,对于数据一般是不能删除的(涉及到后面的数据分析),这就涉及到逻辑删除。所谓逻辑删除指的是数据并不是真正的删除,只是改数据表对应数据的状态(数据表中通常有一列叫delFlag,以此标识正常状态或删除状态)。逻辑删除一般都是更新操作,但写大量更新方法无疑是增加代码量,MyBatis-Plus又是如何实现逻辑删除的呢?其实很简单一共两步。

一、配置文件配置


mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

二、数据模型实体添加注解

使用 @TableLogic注解

package com.blog.tutorial.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import java.util.Date;
/**
 * <p>
 * * </p>
 * * @author youcong
 * @since 2020-04-18
 */@Data
@TableName("wp_users")
public class Users extends Model<Users> {
    private static final long serialVersionUID = 1L;
    @TableId(value = "ID", type = IdType.AUTO)
    private Long id;
    @TableField("user_login")
    private String userLogin;
    @TableField("user_pass")
    private String userPass;
    @TableField("user_nicename")
    private String userNicename;
    @TableField("user_email")
    private String userEmail;
    @TableField("user_url")
    private String userUrl;
    @TableField("user_registered")
    private Date userRegistered;
    @TableField("user_activation_key")
    private String userActivationKey;
    @TableLogic
 @TableField("user_status")
    private Integer userStatus;
    @TableField("display_name")
    private String displayName;
}

完成上面两步,在第三步我们可以进行测试。

三、测试

删除用户表的这个用户,如图:
image.png

数据是否删除在于user_status是否改变,如果变成1,说明逻辑删除生效。
如果user_status还是为0或者这条数据不在了(物理删除了),说明逻辑删除失效。

测试Controller代码:

@PostMapping("/del")
public Map del(Integer id) {
    Map<String, Object> returnMap = new HashMap<>();
    returnMap.put("code", "200");
    returnMap.put("msg", "删除成功");
    usersService.removeById(id);
    return returnMap;
}

使用PostMan测试,如图:
image.png

控制台打印,如图:
image.png

刷新数据表,查看该用户数据是否变动,如图:
image.png
果然变动了,说明逻辑删除生效。

查看原文

赞 2 收藏 1 评论 0

youcongtech 发布了文章 · 11月14日

MyBatis-Plus之分页插件使用

分页在企业级系统必不可少,特别是早年开发人员,在sql里写limit。后来随着技术不断更新升级,向limit我们无需手写,可以通过插件来实现,插件本质上就是limit(在查询语句后面添加limit来做分页)。

MyBatis有PageHelper做分页,MyBatis-Plus只需添加一个配置(spring)或配置类修改就能实现分页。

一、配置类(关键是分页插件)

package com.blog.tutorial.config;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @Author: fun
 * @Description: 分页拦截器
 * @Date: 2020/9/10 10:26
 * @Version: 1.0.0
 */@Configuration
public class MybatisPlusConfig {
    /**
 * 注入主键生成器 */ @Bean
 public IKeyGenerator keyGenerator() {
        return new H2KeyGenerator();
    }
    /**
 * 分页插件 */ @Bean
 public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

二、编写DAO和XML

package com.blog.tutorial.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.blog.tutorial.entity.Users;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
 * <p>
 * Mapper 接口 * </p>
 * * @author youcong
 * @since 2020-04-18
 */@Repository
public interface UsersDao extends BaseMapper<Users> {
    IPage<Users> selectPageVo(Page<Users> page);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blog.tutorial.dao.UsersDao">
    <!-- 通用查询映射结果 -->
 <resultMap id="BaseResultMap" type="com.blog.tutorial.entity.Users">
        <id column="ID" property="id"/>
        <result column="user_login" property="userLogin"/>
        <result column="user_pass" property="userPass"/>
        <result column="user_nicename" property="userNicename"/>
        <result column="user_email" property="userEmail"/>
        <result column="user_url" property="userUrl"/>
        <result column="user_registered" property="userRegistered"/>
        <result column="user_activation_key" property="userActivationKey"/>
        <result column="user_status" property="userStatus"/>
        <result column="display_name" property="displayName"/>
    </resultMap>
    <!-- 通用查询结果列 -->
 <sql id="Base_Column_List">
        ID AS id, user_login AS userLogin, user_pass AS userPass, user_nicename AS userNicename, user_email AS userEmail, user_url AS userUrl, user_registered AS userRegistered, user_activation_key AS userActivationKey, user_status AS userStatus, display_name AS displayName
    </sql>
    <!-- 查询用户列表 -->
 <select id="selectPageVo" resultMap="BaseResultMap">
        select
        ID,user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name
        from
        wp_users
    </select>
</mapper>

三、编写service及其实现类

package com.blog.tutorial.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.blog.tutorial.entity.Users;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:26
 */public interface UsersService extends IService<Users> {
    IPage<Users> selectUserPage(Page<Users> page);
}
package com.blog.tutorial.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blog.tutorial.dao.UsersDao;
import com.blog.tutorial.entity.Users;
import com.blog.tutorial.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@Service
public class UsersServiceImpl extends ServiceImpl<UsersDao, Users> implements UsersService {
    @Autowired
 private UsersDao usersDao;
    @Override
 public IPage<Users> selectUserPage(Page<Users> page) {
        return usersDao.selectPageVo(page);
    }
}

四、编写Controller

package com.blog.tutorial.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.blog.tutorial.entity.Users;
import com.blog.tutorial.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
 private UsersService usersService;
    @GetMapping("/list")
    public Map list(@RequestParam Integer pageNum, @RequestParam Integer pageSize) {
        Map<String, Object> returnMap = new HashMap<>();
        Page<Users> page = new Page<>(pageNum, pageSize);
        IPage<Users> data = usersService.selectUserPage(page);
        returnMap.put("count", data.getTotal());
        returnMap.put("data", data.getRecords());
        return returnMap;
    }
}

五、测试

效果图如下:
image.png

数据刚好十条,并且有总条数。

六、总结

本次例子用的是自己写的sql并结合MyBatis-Plus分页插件来实现分页的,适用场景比较广,通常是多表关联的查询语句等。如果是单表的话,直接用MyBatis-Plus单表自带的分页API即可。

查看原文

赞 3 收藏 2 评论 0

youcongtech 发布了文章 · 11月14日

MyBatis-Plus之实现多数据源

多数据源的目的在于一个代码模块可调用多个数据库的数据进行某些业务操作。

MyBatis-Plus开发者写了一个多数据源叫dynamic-datasource-spring-boot-starter,非常简单易用。

dynamic-datasource-spring-boot-starter文档

官方文档部分截图:
image.png
第三方集成的,基本上是目前比较主流的(用的比较多)。

一、添加Maven依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>2.5.4</version>
</dependency>

二、配置文件修改(application.yml)

spring:
  datasource:
    dynamic:
      primary: db1 #设置默认的数据源,默认值为master
      datasource:
        db1:  #数据源db1
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/wordpress_master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
        db2: #数据源db2
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/wordpress_slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        initial-size: 10
        max-active: 100
        min-idle: 10
        max-wait: 60000
        pool-prepared-statements: true
        max-pool-prepared-statement-per-connection-size: 20
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        #Oracle需要打开注释
        #validation-query: SELECT 1 FROM DUAL
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        stat-view-servlet:
          enabled: true
          url-pattern: /druid/*
          #login-username: admin
          #login-password: admin
        filter:
          stat:
            log-slow-sql: true
            slow-sql-millis: 1000
            merge-sql: false
          wall:
            config:
              multi-statement-allow: true

三、完成成1、2步后,启动应用

如果控制台不报错且出现如下图所示,就表示成功整合:
image.png

四、注意事项

启动主类需要排除Druid相关依赖,否则会出现如下错误:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

解决办法,加上如下代码即可:

@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)

本文部分内容来源我的博客。

查看原文

赞 2 收藏 1 评论 0

youcongtech 发布了文章 · 11月14日

SpringBoot集成Swagger-Bootstrap-UI

之前在创业公司待的时候,用过swagger,因为我第一天来这家公司工作,第一个任务就是做接口文档自动化。

后来觉得它不太好用,在浏览技术网站的时候,偶然发现swagger-bootstrap-ui,于是便重构了,把swagger-bootstrap-ui整合进来,后来发现不仅仅对我们后端有帮助,主要方便我们将接口进行归类,同样对安卓小伙伴也有帮助,他们可以看这个接口文档进行联调。当初我使用swagger-boostrap-ui的时候,那个时候还是1.x版本,如今swagger-bootsrap-ui到2.x,同时也更改名字knife4j,适用场景从过去的单体到微服务。也算是见证咱们国人自己的开源项目从小到大。

该开源项目GitHub地址:
https://github.com/xiaoymin/S...

该开源项目中文文档地址:
https://doc.xiaominfo.com/

一、添加Maven依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.6</version>
</dependency>

二、添加配置类

package com.blog.tutorial.config;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 15:46
 */@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
public class SwaggerConfiguration {
    @Bean
 public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.blog.tutorial.controller"))
                .paths(PathSelectors.any())
                .build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("swagger-bootstrap-ui RESTful APIs")
                .description("swagger-bootstrap-ui")
                .termsOfServiceUrl("http://localhost:5050/")
                .contact("developer@mail.com")
                .version("1.0")
                .build();
    }
}

三、启动项目

启动项目,不报错,然后访问地址:
http://ip:port/doc.html 即可

效果图,如下:
image.png

测试接口,效果图如下:
image.png

调式相当于用PostMan测试接口。

四、常用注解

和swagger一样,swagger用的注解,swagger-bootstrap-ui仍能用。
不过结合我的开发经验来看,最常用的也就两个,@Api和@ApiOperation。
@Api的效果,如图:
image.png

@ApiOperation的效果,如图:
image.png
由此,我们很容易就看出来,它们的含义是什么,一个是接口分类说明,一个是接口方法说明。

至于这里不用swagger的参数注解,主要原因是不想加太多的注解从而增加代码的数量,造成太多冗余。

例子中的Controller代码:

package com.blog.tutorial.controller;
import com.blog.tutorial.entity.Users;
import com.blog.tutorial.service.UsersService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@RestController
@RequestMapping("/user")
@Api(tags = {"用户管理"}, description = "用户管理")
public class UserController {
    @Autowired
 private UsersService usersService;
    @GetMapping("/list")
    @ApiOperation(value = "用户列表")
    public List<Users> list() {
        return usersService.list();
    }
}

五、其它

关于swagger整合系列,可以参考如下:
MP实战系列\(二\)之集成swagger

关于swagger-bootstrap整合系列,可以参考:

MP实战系列\(八\)之SpringBoot+Swagger2

springfox-swagger之swagger-bootstrap-ui

六、可能遇到的问题

1.访问不到接口文档界面白版

一般是被拦截了(shiro或springsecurity机制)或者是配置错误。

2.访问接口文档界面出来了,但扫描不到接口

主要是配置类的缘故,配置类有个包扫描,必须配置为controller路径。
如图所示:
image.png

如果还有其它问题,可以去官方文档上找,官方文档有一个常规问题列表和解决方案,如图所示:
image.png

如果问题非常奇葩的话,实在解决不了(在参考官方文档说明和搜索的前提下,仍解决不了,把问题详细描述和关键性代码提到该开源项目的issue上,向创造者求助)。

查看原文

赞 3 收藏 1 评论 0

youcongtech 发布了文章 · 11月14日

SpringBoot整合MyBatis-Plus3.x

很长时间没有维护这个专栏了,近来开始实行的一个"DZ"计划,计划的其中一个内容就是促使我写下去,给曾经的"出书梦"做个了结。

当初在创业公司的时候,使用MyBatis-Plus的版本还是2.x,如今已经更到了3.4.1,我的作品也应该与时俱进,于是决定全部升级为3.x,包括我自己的Blog产品。

一、导入Maven依赖

pom.xml


<properties>
    <java.version>1.8</java.version>
    <druid-spring-boot-starter.version>1.1.13</druid-spring-boot-starter.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-extension</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid-spring-boot-starter.version}</version>
    </dependency>
    <!-- SpringBoot Web -->
 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Mysql Connector -->
 <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>~~~~

二、编写实体模型

Users.java


package com.blog.tutorial.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import java.util.Date;
/**
 * <p>
 * * </p>
 * * @author youcong
 * @since 2020-04-18
 */@Data
@TableName("wp_users")
public class Users extends Model<Users> {
    private static final long serialVersionUID = 1L;
    @TableId(value = "ID", type = IdType.AUTO)
    private Long id;
    @TableField("user_login")
    private String userLogin;
    @TableField("user_pass")
    private String userPass;
    @TableField("user_nicename")
    private String userNicename;
    @TableField("user_email")
    private String userEmail;
    @TableField("user_url")
    private String userUrl;
    @TableField("user_registered")
    private Date userRegistered;
    @TableField("user_activation_key")
    private String userActivationKey;
    @TableField("user_status")
    private Integer userStatus;
    @TableField("display_name")
    private String displayName;
}

三、编写DAO和对应的XML

UserDao.java

package com.blog.tutorial.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.blog.tutorial.entity.Users;
import org.springframework.stereotype.Repository;
/**
 * <p>
 * Mapper 接口 * </p>
 * * @author youcong
 * @since 2020-04-18
 */@Repository
public interface UsersDao extends BaseMapper<Users> {
}

UserDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blog.tutorial.dao.UsersDao">
    <!-- 通用查询映射结果 -->
 <resultMap id="BaseResultMap" type="com.blog.tutorial.entity.Users">
        <id column="ID" property="id" />
        <result column="user_login" property="userLogin" />
        <result column="user_pass" property="userPass" />
        <result column="user_nicename" property="userNicename" />
        <result column="user_email" property="userEmail" />
        <result column="user_url" property="userUrl" />
        <result column="user_registered" property="userRegistered" />
        <result column="user_activation_key" property="userActivationKey" />
        <result column="user_status" property="userStatus" />
        <result column="display_name" property="displayName" />
    </resultMap>
    <!-- 通用查询结果列 -->
 <sql id="Base_Column_List">
        ID AS id, user_login AS userLogin, user_pass AS userPass, user_nicename AS userNicename, user_email AS userEmail, user_url AS userUrl, user_registered AS userRegistered, user_activation_key AS userActivationKey, user_status AS userStatus, display_name AS displayName
    </sql>
</mapper>

四、编写Service和实现类

UsersService.java

package com.blog.tutorial.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.blog.tutorial.entity.Users;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:26
 */public interface UsersService extends IService<Users> {
}

UserServiceImpl.java

package com.blog.tutorial.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blog.tutorial.dao.UsersDao;
import com.blog.tutorial.entity.Users;
import com.blog.tutorial.service.UsersService;
import org.springframework.stereotype.Service;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@Service
public class UsersServiceImpl extends ServiceImpl<UsersDao, Users> implements UsersService {
}

五、编写Controller

package com.blog.tutorial.controller;
import com.blog.tutorial.entity.Users;
import com.blog.tutorial.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 13:27
 */@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
 private UsersService usersService;
    @GetMapping("/list")
    public List<Users> list() {
        return usersService.list();
    }
}

六、编写启动类

Application.java

package com.blog.tutorial;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
 * @description:
 * @author: youcong
 * @time: 2020/11/14 11:32
 */
@EnableTransactionManagement
@SpringBootApplication
@MapperScan("com.blog.tutorial.dao")
public class BlogApplication {
    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args);
        System.out.println("====服务启动成功====");
    }
}

七、编写配置文件application.yml

server:
  port: 5050
spring:
  # 配置数据源
 datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/wordpress_test?useUnicode=true&characterEncoding=utf-8&serverTimeZone=GMT
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
# mybatis-plus相关配置
mybatis-plus:
  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
 mapper-locations: classpath:mapper/*.xml
  # 以下配置均有默认值,可以不设置
 global-config:
    db-config:
      #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
 id-type: auto
 #字段策略 IGNORED:"忽略判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"
 field-strategy: NOT_EMPTY
      #数据库类型
 db-type: MYSQL
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
 map-underscore-to-camel-case: true
 # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
 call-setters-on-nulls: true
 # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

八、启动项目

启动项目不报错,通常表示是没有问题的。

九、测试

测试的目的在于看MyBatis-Plus是否真的整成功了。
写了一个查询列表的接口,效果图如下:
image.png

查看原文

赞 2 收藏 1 评论 1

youcongtech 发布了文章 · 2018-12-22

MyBatis集成SpringMVC

本章主要内容包含SpringMVC简介、MyBatis整合SpringMVC(主要是在前面的MyBatis整合Spring基础上进行)、Spring应用实例等。

1.1 SpringMVC简介

1.1.1 介绍

SpringMVC因Spring的名气而得到大范围地应用,当然了,除此之外SpringMVC的确简单易学,同时其本身与Struts2比较够轻量。

1.1.2 SpringMVC与Struts2比较

比较归纳为如下:

(1)入口不同:SpringMVC的入口是Servlet,Struts的入口是Filter。

(2)性能上:spring3 mvc是方法级别的拦截,拦截到方法后根据参数上的注解,把request数据注入进去,在spring3 mvc中,一个方法对应一个request上下文。而struts2框架是类级别的拦截,每次来了请求就创建一个Action,然后调用setter getter方法把request中的数据注入;struts2实际上是通过setter getter方法与request打交道的;struts2中,一个Action对象对应一个request上下文。

(3)拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大。

(4)设计思想上,Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展。

(5)SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。

(6)Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高(当然Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml配置的地方不少)。

1.1.3 MVC模式

三层架构在Java中是比较出名的,三层架构无非是数据访问层、业务逻辑层、Web层(又称UI层)等。
其中Web层中就涉及到MVC模式(模型-视图-控制器),MVC模式可以以SpringMVC官方网站的一张图来表示:

clipboard.png

1.2 整合SpringMVC

一般通常整合SpringMVC并不需要花多大力气,很简单的。如果是Spring+SpringMVC+MyBatis整合,最好还是先将Spring+MyBatis先整合好,这样可以省去很多不必要的麻烦。然后就可以无缝集成SpringMVC。

1.2.1 导入依赖

依赖复用可以参考MyBatis集成Spring

直接可以将里面的依赖复用

1.2.2 编写HelloController

package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.ui.ModelMap;
@Controller
@RequestMapping("/hello")
public class HelloController{ 
   @RequestMapping(method = RequestMethod.GET)
   public String printHello(ModelMap model) {
      model.addAttribute("message", "Hello Spring MVC Framework!");
      return "hello";
   }
}

1.2.3 修改web.xml

<web-app id="WebApp_ID" version="2.4"
   xmlns="http://java.sun.com/xml/ns/j2ee" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

   <display-name>Spring MVC Application</display-name>

   <servlet>
    <description>spring mvc servlet</description>
    <servlet-name>springMvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:application-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

1.2.4 编写application-mvc.xml

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
   http://www.springframework.org/schema/beans     
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <context:component-scan base-package="com.tutorialspoint" />

   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/jsp/" />
      <property name="suffix" value=".jsp" />
   </bean>

</beans>

1.2.5 在WEB-INF文件夹下新建jsp文件夹,并在jsp文件夹新建hello.jsp文件

<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
   <h2>${message}</h2>
</body>
</html>

1.2.6 启动项目并在浏览器输入对应的地址会显示对应的视图结果

clipboard.png

1.3 SpringMVC应用实例

应用实例不少以下只列出这么几个?
(1)异常处理;
(2)文件上传;
(3)跨域请求;
(4)表单处理;
(5)重定向;

1.3.1 异常处理

异常处理,对于项目开发至关重要,总不能用户点击一个页面出错了,直接报500,那样用户体验多不好啊!

所以这里讲的是SpringMVC对异常的处理,希望能给大家带来一定的 帮助和启发。

(1)编写实体

package com.tutorialspoint;
public class Student {
   private Integer age;
   private String name;
   private Integer id;
   public void setAge(Integer age) {
      this.age = age;
   }
   public Integer getAge() {
      return age;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   public Integer getId() {
      return id;
   }
}

(2)编写异常

package com.tutorialspoint;
public class SpringException extends RuntimeException{
   private String exceptionMsg;   
   public SpringException(String exceptionMsg) {
      this.exceptionMsg = exceptionMsg;
   }   
   public String getExceptionMsg(){
      return this.exceptionMsg;
   }   
   public void setExceptionMsg(String exceptionMsg) {
      this.exceptionMsg = exceptionMsg;
   }
}

(3)编写测试Controller

package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.ui.ModelMap;
@Controller
public class StudentController {
   @RequestMapping(value = "/student", method = RequestMethod.GET)
   public ModelAndView student() {
      return new ModelAndView("student", "command", new Student());
   }
   @RequestMapping(value = "/addStudent", method = RequestMethod.POST)
   @ExceptionHandler({SpringException.class})
   public String addStudent( @ModelAttribute("HelloWeb")Student student, 
      ModelMap model) {
      if(student.getName().length() < 5 ){
         throw new SpringException("Given name is too short");
      }else{
       model.addAttribute("name", student.getName());
      }     
      if( student.getAge() < 10 ){
         throw new SpringException("Given age is too low");
      }else{
       model.addAttribute("age", student.getAge());
      }
      model.addAttribute("id", student.getId());
      return "result";
   }
}

(3)在application-mvc.xml补充如下内容


<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
   <property name="exceptionMappings">
      <props>
         <prop key="com.tutorialspoint.SpringException">
            ExceptionPage
         </prop>
      </props>
   </property>
   <property name="defaultErrorView" value="error"/>
</bean>

(4)编写JSP

student.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring MVC Exception Handling</title>
</head>
<body>

<h2>Student Information</h2>
<form:form method="POST" action="/HelloWeb/addStudent">
   <table>
   <tr>
   <td><form:label path="name">Name</form:label></td>
   <td><form:input path="name" /></td>
   </tr>
   <tr>
   <td><form:label path="age">Age</form:label></td>
   <td><form:input path="age" /></td>
   </tr>
   <tr>
   <td><form:label path="id">id</form:label></td>
   <td><form:input path="id" /></td>
   </tr>
   <tr>
   <td colspan="2">
   <input type="submit" value="Submit"/>
   </td>
   </tr>
   </table>  
</form:form>
</body>
</html>

error.jsp

<html>
<head>
    <title>Spring Error Page</title>
</head>
<body>

<p>An error occured, please contact webmaster.</p>

</body>
</html>

ExceptionPage.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring MVC Exception Handling</title>
</head>
<body>

<h2>Spring MVC Exception Handling</h2>

<h3>${exception.exceptionMsg}</h3>

</body>
</html>

result.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring MVC Form Handling</title>
</head>
<body>

<h2>Submitted Student Information</h2>
   <table>
   <tr>
   <td>Name</td>
   <td>${name}</td>
   </tr>
   <tr>
   <td>Age</td>
   <td>${age}</td>
   </tr>
   <tr>
   <td>ID</td>
   <td>${id}</td>
   </tr>
   </table>  
</body>
</html>

(5)运行项目
出现如图所示,表示成功

clipboard.png

在SpringMVC中有两种处理异常的方式,那么就存在一个优先级的问题:

当发生异常的时候,SpringMVC会如下处理:

a.SpringMVC会先从配置文件找异常解析器HandlerExceptionResolver

b.如果找到了异常异常解析器,那么接下来就会判断该异常解析器能否处理当前发生的异常

c.如果可以处理的话,那么就进行处理,然后给前台返回对应的异常视图

d.如果没有找到对应的异常解析器或者是找到的异常解析器不能处理当前的异常的时候,就看当前的Controller中有没有提供对应的异常处理器,如果提供了就由Controller自己进行处理并返回对应的视图

e.如果配置文件里面没有定义对应的异常解析器,而当前Controller中也没有定义的话,那么该异常就会被抛出来。

1.3.2 文件上传

传统的图片服务器ftp,就现在而言已经没几个人在用了,当然了,wordpress相关的插件安装和主题下载就用到ftp。

当然了,还有不少企业将上传文件(包含图片等)放入线上tomcat某个文件夹下或者项目里面,这样的弊端使项目会越来越庞大,之前庞大是因为不断增长的需求,代码不得不越多,因此也会扩充容量。

不过目前很多企业通常采用比如腾讯云、阿里云、七牛云等对象存储,作为图片存储。

今天我们就以腾讯云的对象存储为例。

(1)导入依赖

<!-- 腾讯云 -->
<dependency>
       <groupId>com.qcloud</groupId>
       <artifactId>cos_api</artifactId>
       <version>5.2.4</version>
</dependency>

(2)编写工具类

package com.custome;

import com.qcloud.cos.COSClient;  
import com.qcloud.cos.ClientConfig;  
import com.qcloud.cos.auth.BasicCOSCredentials;  
import com.qcloud.cos.auth.COSCredentials;  
import com.qcloud.cos.model.ObjectMetadata;  
import com.qcloud.cos.model.PutObjectResult;  
import com.qcloud.cos.region.Region;  
import org.springframework.web.multipart.MultipartFile;  
import java.io.*;  
import java.net.URL;  
import java.util.Date;  
import java.util.Random; 

public class COSClientUtil {
    
    //todo 这些变量信息自行到 腾讯云对象存储控制台 获取  
 
    private COSClient cOSClient;  
  
    private static final String ENDPOINT = "test.com";  //用户可以指定域名,不指定则为默认生成的域名
    
    //secretId   
    private static final String secretId = "AKIDCJ";
    // secretKey 
    private static final String secretKey = "CD7";
    // 存储通名称    
    private static final String bucketName = "test";//公有读私有写
// 1 初始化用户身份信息(secretId, secretKey)  
    private static COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
    // 2 设置bucket的区域, COS地域的简称请参照 https://cloud.tencent.com/document/product/436/6224  
    private static ClientConfig clientConfig = new ClientConfig(new Region("ap-beijing-1"));
    // 3 生成cos客户端  
    private static COSClient cosclient = new COSClient(cred, clientConfig);
    
    public COSClientUtil() {  
        cOSClient = new COSClient(cred, clientConfig);  
    }  
  
    /** 
     * 销毁 
     */  
    public void destory() {  
        cOSClient.shutdown();  
    }  
  
    /** 
     * 上传图片 
     * 
     * @param url 
     */  
    public void uploadImg2Cos(String url) throws Exception {  
        File fileOnServer = new File(url);  
        FileInputStream fin;  
        try {  
            fin = new FileInputStream(fileOnServer);  
            String[] split = url.split("/");  
            this.uploadFile2Cos(fin, split[split.length - 1]);  
        } catch (FileNotFoundException e) {  
            throw new Exception("图片上传失败");  
        }  
    }  
  
    public String uploadFile2Cos(MultipartFile file) throws Exception {  
        if (file.getSize() > 10 * 1024 * 1024) {  
            throw new Exception("上传图片大小不能超过10M!");  
        }  
        //图片名称
        String originalFilename = file.getOriginalFilename();  
        System.out.println("originalFilename = " + originalFilename);
        //图片后缀
        String substring = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();  
        System.out.println("substring = " + substring);
        Random random = new Random();  
        //生成新的图片名称(随机数0-9999+系统当前时间+上传图片名)
        String name = random.nextInt(10000) + System.currentTimeMillis() + "_" + substring;  
        try {  
            InputStream inputStream = file.getInputStream();  
            this.uploadFile2Cos(inputStream, name);  
            return name;  
        } catch (Exception e) {  
            throw new Exception("图片上传失败");  
        }  
    }  
  
    /** 
     * 获得图片路径 
     * 
     * @param fileUrl 
     * @return 
     */  
    public String getImgUrl(String fileUrl) {  
        return getUrl(fileUrl);  
    }  
  
    /** 
     * 获得url链接 
     * 
     * @param key 
     * @return 
     */  
    public String getUrl(String key) {  
        // 设置URL过期时间为10年 3600l* 1000*24*365*10  
        Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 10);  
        // 生成URL  
        URL url = cOSClient.generatePresignedUrl(bucketName, key, expiration);  
        if (url != null) {  
            return url.toString();  
        }  
        return null;  
    }  
  
    /** 
     * 上传到COS服务器 如果同名文件会覆盖服务器上的 
     * 
     * @param instream 
     *            文件流 
     * @param fileName 
     *            文件名称 包括后缀名 
     * @return 出错返回"" ,唯一MD5数字签名 
     */  
    public String uploadFile2Cos(InputStream instream, String fileName) {  
        String ret = "";  
        try {  
            // 创建上传Object的Metadata  
            ObjectMetadata objectMetadata = new ObjectMetadata();  
            objectMetadata.setContentLength(instream.available());  
            objectMetadata.setCacheControl("no-cache");  
            objectMetadata.setHeader("Pragma", "no-cache");  
            objectMetadata.setContentType(getcontentType(fileName.substring(fileName.lastIndexOf("."))));  
            objectMetadata.setContentDisposition("inline;filename=" + fileName);  
            // 上传文件  
            PutObjectResult putResult = cOSClient.putObject(bucketName,  fileName, instream, objectMetadata);  
            ret = putResult.getETag();  
        } catch (IOException e) {  
            e.printStackTrace();  
        } finally {  
            try {  
                if (instream != null) {  
                    instream.close();  
                }  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        return ret;  
    }  
  
    /** 
     * Description: 判断Cos服务文件上传时文件的contentType 
     * 
     * @param filenameExtension 文件后缀 
     * @return String 
     */  
    public static String getcontentType(String filenameExtension) {  
        if (filenameExtension.equalsIgnoreCase("bmp")) {  
            return "image/bmp";  
        }  
        if (filenameExtension.equalsIgnoreCase("gif")) {  
            return "image/gif";  
        }  
        if (filenameExtension.equalsIgnoreCase("jpeg") || filenameExtension.equalsIgnoreCase("jpg")  
                || filenameExtension.equalsIgnoreCase("png")) {  
            return "image/jpeg";  
        }  
        if (filenameExtension.equalsIgnoreCase("html")) {  
            return "text/html";  
        }  
        if (filenameExtension.equalsIgnoreCase("txt")) {  
            return "text/plain";  
        }  
        if (filenameExtension.equalsIgnoreCase("vsd")) {  
            return "application/vnd.visio";  
        }  
        if (filenameExtension.equalsIgnoreCase("pptx") || filenameExtension.equalsIgnoreCase("ppt")) {  
            return "application/vnd.ms-powerpoint";  
        }  
        if (filenameExtension.equalsIgnoreCase("docx") || filenameExtension.equalsIgnoreCase("doc")) {  
            return "application/msword";  
        }  
        if (filenameExtension.equalsIgnoreCase("xml")) {  
            return "text/xml";  
        }  
        return "image/jpeg";  
    }  
    
}

(3)编写测试Controller

@PostMapping(value="/uploadPicture",produces="application/json;charset=utf-8")
    public JSONObject upModify(HttpServletRequest request, MultipartFile file) {
        
        JSONObject json = new JSONObject();

        try {
            
            COSClientUtil cosClientUtil = new COSClientUtil(); 
            
            //获取Cookie
            String cookie = CookieUtils.getCookie(request,"userCode");
            
            //解密后的userCode
            String decodeStr = Base64.decodeStr(cookie);
            
            
        
            if(!file.isEmpty()) {
                
                String name = cosClientUtil.uploadFile2Cos(file); 
                //图片名称
                System.out.println("name = " + name);
                //上传到腾讯云
                String img_url = cosClientUtil.getImgUrl(name); 
                System.out.println("img_url = " + img_url);
                
                //数据库保存图片地址
                String db_img_url = img_url.substring(0,img_url.indexOf("?"));
                System.out.println("db_img_url = " + db_img_url);
                
                SysUser user = new SysUser();
                user.setUserCode(decodeStr);
                user.setAvatar(db_img_url);
                
                //调用修改逻辑
                boolean isModifyUser = userService.updateById(user);
                
                if(isModifyUser) {
                    
                    json.put("returnCode", "000000");
                    json.put("returnMsg", "上传文件成功");

                }else {
                    json.put("returnCode", "111111");
                    json.put("returnMsg", "上传文件失败");
                
                }

            }else {
                json.put("returnCode", "222222");
                json.put("returnMsg", "参数异常");
            }
            
        } catch (Exception e) {
            e.printStackTrace();

            json.put("returnCode", "333333");
            json.put("returnMsg", "特殊异常");
    
        }
        
        return json;
    }

(4)编写简单的html测试

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>上传图片测试</title>
<script type="text/javascript" data-original="../js/jquery-1.8.0.min.js"></script>
<script type="text/javascript">
function setImg(obj){//用于进行图片上传,返回地址
    var f = $(obj).val();
    if(f == null || f == undefined || f == ''){
        return false;
    }
    if(!/\.(?:png|jpg|bmp|gif|PNG|JPG|BMP|GIF)$/.test(f))
    {
        alertLayel("类型必须是图片(.png|jpg|bmp|gif|PNG|JPG|BMP|GIF)");
        $(obj).val('');
        return false;
    }

    var data = new FormData();

    $.each($(obj)[0].files,function(i,file){
        data.append('file', file);
    });
    
    var upload_img = $("#uploadinput")[0].files[0];

    var url = window.URL.createObjectURL(upload_img);
    
    $.ajax({
        type: "POST",
        url: "uploadPicture",
        data: data,
        cache: false,
        contentType: false,     //必须false才会自动加上正确的Content-Type  
        processData: false,     //必须false才会自动加上正确的Content-Type  
        dataType:"json",
        success: function(data) {
            alert(data.returnMsg)
            
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
             alert(XMLHttpRequest.status);
             // 状态
             alert(XMLHttpRequest.readyState);
             // 错误信息   
             alert(textStatus);
        }
    });
}

</script>
</head>
<body>
 <input type="file" id="uploadinput" name="file" onchange="setImg(this)"/>
</body>
</html>

1.3.3 跨域请求

(1)编写拦截器

package com.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class CORSInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                             Object handler) throws Exception {

        response.addHeader("Access-Control-Allow-Origin", "*");
        
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {

    }
}

(2)application-mvc.xml配置拦截器

<mvc:interceptors>
           <mvc:interceptor>
            <mvc:mapping path="/**"/>
                <bean class="com.interceptor.CORSInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

(3)编写html测试

<html>

<head>
    <meta charset="utf-8">
<script data-original="jquery-1.8.0.min.js"></script>
<script>
function test(){
var str="test";
$.ajax({
        url:"http://192.168.1.125:8080/test-web/user/getCookie",
        type:"POST",
        data : {"str":str},
        dataType : 'json',
        success:function(data){
            
            alert(data.returnMsg);
            
        },error:function(XMLHttpRequest, textStatus, errorThrown){
             alert(XMLHttpRequest.status);
            alert(XMLHttpRequest.readyState);
            alert(textStatus);
        }
    });
}
</script>
</head>
<body onload="test()">
</body>

1.3.4 表单处理

(1)编写实体

实体可以复用前面的,之所以列出来防止一些读者不理解。

package com.tutorialspoint;
public class Student {
   private Integer age;
   private String name;
   private Integer id;
   public void setAge(Integer age) {
      this.age = age;
   }
   public Integer getAge() {
      return age;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   public Integer getId() {
      return id;
   }
}

(2)编写Controller

package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.ui.ModelMap;
@Controller
public class StudentController {
   @RequestMapping(value = "/student", method = RequestMethod.GET)
   public ModelAndView student() {
      return new ModelAndView("student", "command", new Student());
   }   
   @RequestMapping(value = "/addStudent", method = RequestMethod.POST)
   public String addStudent(@ModelAttribute("SpringWeb")Student student, 
   ModelMap model) {
      model.addAttribute("name", student.getName());
      model.addAttribute("age", student.getAge());
      model.addAttribute("id", student.getId());      
      return "result";
   }
}

ModelMap同ModelAndView相同点,将数据已键值对形式返回到前台,而前台只需知道对应的键即可,就可以获得对应的值。

当然就视图与数据分离而言,尽量不要使用ModelAndView,尽量使用Model或者ModelMap也可以。

(3)编写jsp
student.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring MVC Form Handling</title>
</head>
<body>

<h2>Student Information</h2>
<form:form method="POST" action="/spring-example/addStudent">
   <table>
    <tr>
        <td><form:label path="name">Name</form:label></td>
        <td><form:input path="name" /></td>
    </tr>
    <tr>
        <td><form:label path="age">Age</form:label></td>
        <td><form:input path="age" /></td>
    </tr>
    <tr>
        <td><form:label path="id">id</form:label></td>
        <td><form:input path="id" /></td>
    </tr>
    <tr>
        <td colspan="2">
            <input type="submit" value="Submit"/>
        </td>
    </tr>
</table>  
</form:form>
</body>
</html>

result.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring MVC Form Handling</title>
</head>
<body>
 
<h2>Submitted Student Information</h2>
   <table>
    <tr>
        <td>Name</td>
        <td>${name}</td>
    </tr>
    <tr>
        <td>Age</td>
        <td>${age}</td>
    </tr>
    <tr>
        <td>ID</td>
        <td>${id}</td>
    </tr>
</table> 
</body>
</html>

(4)测试
启动应用测试,结果如图:

clipboard.png

1.3.5 重定向

说到重定向不得不提到一个转发。这里概述一下转发与重定向的区别:

重定向和转发有一个重要的不同:当使用转发时,JSP容器将使用一个内部的方法来调用目标页面,新的页面继续处理同一个请求,而浏览器将不会知道这个过程。 与之相反,重定向方式的含义是第一个页面通知浏览器发送一个新的页面请求。因为,当你使用重定向时,浏览器中所显示的URL会变成新页面的URL, 而当使用转发时,该URL会保持不变。重定向的速度比转发慢,因为浏览器还得发出一个新的请求。同时,由于重定向方式产生了一个新的请求,所以经过一次重 定向后,request内的对象将无法使用。
转发和重定向的区别
不要仅仅为了把变量传到下一个页面而使用session作用域,那会无故增大变量的作用域,转发也许可以帮助你解决这个问题。

重定向:以前的request中存放的变量全部失效,并进入一个新的request作用域。
转发:以前的request中存放的变量不会失效,就像把两个页面拼到了一起。

比如session的保存,就是转发的一个应用实例。

Session通过setAttribute以键值对的形式保存Session,而要获得该session,只需getAttribute对应的键即可,当然了,Session也有它的生命周期,即有效期,超过这个有效期则会发生session失效问题。

通常session有效默认为30分钟,可以通过web.xml配置修改



<session-config>
    <session-timeout>60</session-timeout>
</session-config>

(1)编写示例

package com.tutorialspoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class WebController {
   @RequestMapping(value = "/index", method = RequestMethod.GET)
   public String index() {
       return "index";
   }   
   @RequestMapping(value = "/redirect", method = RequestMethod.GET)
   public String redirect() {     
      return "redirect:finalPage";
   }   
   @RequestMapping(value = "/finalPage", method = RequestMethod.GET)
   public String finalPage() {     
      return "final";
   }
}

(2)编写index.jsp和final.jsp
index.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring Page Redirection</title>
</head>
<body>
<h2>Spring Page Redirection</h2>
<p>Click below button to redirect the result to new page</p>
<form:form method="GET" action="/spring-example/redirect">
<table>
    <tr>
    <td>
    <input type="submit" value="Redirect Page"/>
    </td>
    </tr>
</table>  
</form:form>
</body>
</html>

final.jsp

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
    <title>Spring Page Redirection</title>
</head>
<body>

<h2>Redirected Page</h2>

</body>
</html>

(3)启动服务器,输入对应的地址

clipboard.png
点击红色标记处,会出现如图,这样就表示正常,否则可能是500,那就是代码有问题或者环境问题

clipboard.png

1.4 小结

本章内容主要有SpringMVC简介、MyBatis整合SpringMVC(主要是在前面的MyBatis整合Spring基础上进行)、Spring应用实例等。

通过对这些内容的介绍说明和示例讲解,相信你已经学会使用了。

本文部分内容主要来自笔者博客园

查看原文

赞 0 收藏 0 评论 0

youcongtech 发布了文章 · 2018-12-19

MyBatis集成Spring

本章主要内容包括Spring简介、Spring的两大特性(IOC和AOP)、事务MyBatis集成Spring等。
也许有读者会疑惑,明明是MyBatis-Plus实战,怎么还讲MyBatis这么多东西?
其实很简单,MyBatis-Plus是由MyBatis衍生而来的,其实MyBatis-Plus与Spring整合其实与MyBatis跟Spring整合差异并不大,读者通过前面的MyBatis-Plus初步和MyBatis初步这两个章节,我相信读者们如果看过,心中自会明了。

1.1 Spring简介

Spring是企业Java最流行的应用程序开发框架。全球数百万开发人员使用Spring Framework创建高性能,易于测试和可重用的代码。

Spring框架是一个开源Java平台。它最初由Rod Johnson编写,并于2003年6月首次在Apache 2.0许可下发布。

Spring Framework的核心功能可用于开发任何Java应用程序,但有一些扩展用于在Java EE平台之上构建Web应用程序。Spring框架的目标是通过启用基于POJO的编程模型,使J2EE开发更易于使用并促进良好的编程实践。

Spring相关资料:

Spring官方文档

Spring官网

Spring的架构图,如下:

clipboard.png

使用Spring有什么好处?

(1)Spring使开发人员能够使用POJO开发企业级应用程序。仅使用POJO的好处是您不需要EJB容器产品(如应用程序服务器),但您可以选择仅使用强大的servlet容器(如Tomcat)或某些商业产品;

(2)Spring采用模块化方式组织。即使包和类的数量很大,你也只需要担心你需要的那些而忽略其余的;

(3)Spring并没有重新发明轮子,而是真正利用了一些现有技术,如几个ORM框架,日志框架,JEE,Quartz和JDK计时器以及其他视图技术;

(4)测试用Spring编写的应用程序很简单,因为依赖于环境的代码被移动到这个框架中。此外,通过使用JavaBeanstyle POJO,使用依赖注入来注入测试数据变得更加容易;

(5)Spring的Web框架是一个设计良好的Web MVC框架,它提供了一个很好的替代Web框架,如Struts或借着Spring名气流行+自身相比Struts2更轻量级的实现方式的SpringMVC;

(6)Spring提供了一个方便的API,用于将特定于技术的异常(例如,JDBC,Hibernate或JDO抛出)转换为一致的,未经检查的异常;

(7)轻量级IoC容器往往是轻量级的,尤其是与EJB容器相比时。这有利于在具有有限内存和CPU资源的计算机上开发和部署应用程序;

1.2 IOC特性

1.2.1 IOC简介

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

那么什么是依赖注入呢?

我们分别看看这两个词。这里依赖部分转换为两个类之间的关联。例如,A类依赖于B类。现在,让我们看第二部分,注入。所有这些意味着,B类将由IoC注入A类。

依赖注入可以通过将参数传递给构造函数或使用setter方法进行后构建来实现。

1.2.2 IOC实例

下面我自己曾经的一个项目博客系统里面的部分代码为例来讲解

(1)编写数据访问层接口

package cn.blog.mapper;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.blog.entity.Post;
/*
 * 文章接口
 */
public interface PostMapper {
   
    /**
     * 根据文章ID显示文章
     */
    public Post selectByPostId(Integer postId);
    
    /**
     * 显示文章总数
     */
    public int selectPostCount();
    
    
    /**
     *分页显示
     */
    public List<Post> selectPostWherePage(HashMap<String,Object> map);
    
    
    /**
     * 根据用户ID显示该用户的所有文章
     */
    public List<Post> selectUserIdWherePage(Map<String,Object> map);
    
    
    /**
     *显示该用户ID下所有文章总数
     */
    public int selectUserIdWherePageCount(Integer userId);
    
    
    
    
}

(2)编写业务接口

package cn.blog.service;

import org.apache.ibatis.annotations.Param;

import cn.blog.entity.Post;
import cn.blog.utils.PageBean;

public interface PostService {

    
    /**
     * 根据文章ID显示文章
     */
    public Post selectByPostId(Integer postId);
    
    
    /**
     * 显示文章总数
     */
    public int selectPostCount();
    
    
    /**
     *分页显示
     */
    public PageBean<Post> selectPostWherePage(int currentPage);
    
    /**
     * 根据用户ID显示该用户的所有文章
     */
    public PageBean<Post> selectUserIdWherePage(int currentPate,int userId);
    

    /**
     *显示该用户ID下所有文章总数
     */
    public int selectUserIdWherePageCount(Integer userId);
    
     
}

(3)编写业务接口实现类

package cn.blog.service.impl;


import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.blog.entity.Post;
import cn.blog.mapper.PostMapper;
import cn.blog.service.PostService;
import cn.blog.utils.PageBean;


@Service
public class PostServiceImpl implements PostService{

    @Autowired
    private PostMapper postMapper;
    
    @Override
    public Post selectByPostId(Integer postId) {
        // TODO Auto-generated method stub
        return postMapper.selectByPostId(postId);
    }

    @Override
    public int selectPostCount() {
        // TODO Auto-generated method stub
        return postMapper.selectPostCount();
    }

    @Override
    public PageBean<Post> selectPostWherePage(int currentPage) {
            HashMap<String,Object> map = new HashMap<String,Object>();
            PageBean<Post> pageBean = new PageBean<Post>();

            //封装当前页数
            pageBean.setCurrPage(currentPage);

            //每页显示的数据
            int pageSize=5;
            pageBean.setPageSize(pageSize);
            
            //封装总记录数
            int totalCount = postMapper.selectPostCount();
            pageBean.setTotalCount(totalCount);
            
            //封装总页数
            double tc = totalCount;
            Double num =Math.ceil(tc/pageSize);//向上取整
            pageBean.setTotalPage(num.intValue());

            map.put("start",(currentPage-1)*pageSize);
            map.put("size", pageSize);
            //封装每页显示的数据
            List<Post> lists = postMapper.selectPostWherePage(map);
            pageBean.setLists(lists);
            

            return pageBean;
    }



    @Override
    public int selectUserIdWherePageCount(Integer userId) {
        // TODO Auto-generated method stub
        return postMapper.selectUserIdWherePageCount(userId);
    }

    @Override
    public PageBean<Post> selectUserIdWherePage(int currentPage,int userId) {

        
        PageBean<Post> pageBean = new PageBean<Post>();

        Map<String,Object> paramMap = new HashMap<String,Object>();
        //封装当前页数

        pageBean.setCurrPage(currentPage);

        //每页显示的数据
        int pageSize=5;
        pageBean.setPageSize(pageSize);

        //封装总记录数
        int totalCount = postMapper.selectUserIdWherePageCount(userId);
        pageBean.setTotalCount(totalCount);

        //封装总页数
        double tc = totalCount;
        Double num =Math.ceil(tc/pageSize);//向上取整
        pageBean.setTotalPage(num.intValue());
        paramMap.put("start", (currentPage-1)*pageSize);
        paramMap.put("size", pageBean.getPageSize());
        paramMap.put("userId", userId);

        //封装每页显示的数据
        List<Post> lists = postMapper.selectUserIdWherePage(paramMap);
        
        pageBean.setLists(lists);
        

        return pageBean;
    }

}

(4)对应的Spring的xml文件内容

<bean id="postMapper" class="com.blog.mapper.PostMapper">
</bean>

<bean id="postService" class="cn.blog.service.impl.PostServiceImpl">
<property name="postMapper" ref="postMapper"/>
</bean>

通过这段例子,我想大家应该明白了什么是依赖注入,如果不这样写的话,你前台要想获得对应的数据访问层(DAO层)数据,必须得实例化,十几二十个还好管理,成千上万个呢?那估计你肯定会有骂娘的冲动了。同时也明白了Spring为你管理对象的好处。如果没有Spring为你管理对象,一个一个实例化,那将是一件非常恐惧的事情。

1.3 AOP特性

1.3.1 AOP简介

Spring的一个关键组件是面向切面编程(AOP)框架。跨越应用程序多个点的功能称为跨领域问题,这些跨领域问题在概念上与应用程序的业务逻辑分开。各方面有各种常见的良好示例,包括日志记录,声明式事务,安全性,缓存等。

OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面。DI可帮助您将应用程序对象彼此分离,而AOP可帮助您将交叉问题与它们所影响的对象分离。

Spring Framework的AOP模块提供了面向方面的编程实现,允许您定义方法拦截器和切入点,以便干净地解耦实现应该分离的功能的代码。

1.3.2 AOP实例

就拿Spring的xml注解事务配置讲解:

(1)spring-mybatis.xml事务配置部分内容

<!-- 配置事务管理 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 事务管理 属性 -->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="append*" propagation="REQUIRED"/>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="modify*" propagation="REQUIRED"/>
            <tx:method name="edit*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="remove*" propagation="REQUIRED"/>
            <tx:method name="repair" propagation="REQUIRED"/>

            <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="load*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="search*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="datagrid*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置切面 -->
    <aop:config>
        <aop:pointcut id="transactionPointcut" expression="execution(* com.blog.service..*.*(..))"/>
        <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/>
    </aop:config>

上述内容,比如配置切面,这个切面相当于不管怎么样你必定会经过的,比如电视中山大王经常去某个必经之路抢经过路人的钱财。

当然了,上面的配置,主要是关于事务的。熟悉xml事务配置的,对于上面xml代码再熟悉不过了。

不熟悉的也没有关系,用多了自然明白。另外补充一点,上面同时也代表了一种规则,比如我的添加方法叫userAdd,但是我的事务配置了必须要以add或者append或者其他read-only为false开头的才能在数据库对应的表新增数据,现在我的添加方法叫userAdd,它就会触发该规则中包含的另外一种规则就是:<tx:method name="*" propagation="REQUIRED" read-only="true"/>,这个规则将非上述列出的均判断为查询,而我的userAdd是新增,但是不符合上述配置的事务规则,就会导致报错,从而插入数据失败。

这样一来,大家或许就知道它的应用了,如果是传统的那种Java编程(非Spring),我们将要写很多判断。

1.4 事务

1.4.1 事务简介

这里说到的事务主要是指数据库事务。数据库事务是一个被视为单一的工作单元的操作序列。这些操作要么完全地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。

事务的概念可以描述为具有以下四个关键属性说成是ACID:

(1)原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的;

(2)一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等;

(3)隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏;

(4)持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。

一个真正的 RDBMS 数据库系统将为每个事务保证所有的四个属性。使用 SQL 发布到数据库中的事务的简单视图如下:

(1)使用 begin transaction 命令开始事务。

(2)使用 SQL 查询语句执行各种删除、更新或插入操作。

(3)如果所有的操作都成功,则执行提交操作,否则回滚所有操作。

Spring 框架在不同的底层事务管理 APIs 的顶部提供了一个抽象层。Spring 的事务支持旨在通过添加事务能力到 POJOs 来提供给 EJB 事务一个选择方案。Spring 支持编程式和声明式事务管理。EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现。

1.4.2 两种事务(局部事务与全局事务)

局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。

局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。

全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。

1.4.3 事务管理的两种形式

Spring 支持两种类型的事务管理:

编程式事务管理:这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护(编程管理事务,一大堆if-else等各种判断,代码量大先不说,但是以后维护起来会使你有种想跳楼的冲动)。

声明式事务管理:这意味着你从业务代码中分离事务管理。你仅仅使用注解或 XML 配置来管理事务(前面的AOP示例就很好地体现了XML管理事务)。

声明式事务管理比编程式事务管理更可取,尽管它不如编程式事务管理灵活,但它允许你通过代码控制事务。但作为一种横切关注点,声明式事务管理可以使用 AOP 方法进行模块化。Spring 支持使用 Spring AOP 框架的声明式事务管理。

1.5 MyBatis集成Spring

1.5.1 新建Maven项目导入依赖

这里的依赖不仅仅包括MyBatis整合Spring的必需依赖,同时也包含SpringMVC的依赖,之所以导入是为了后面的复用。


<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>
  <groupId>cn.demo.test</groupId>
  <artifactId>test001</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
   
   
  <dependencies>
        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!-- spring核心包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.4</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.5.4</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.11</version>
        </dependency>
        <!--mybatis核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.21</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
            <type>jar</type>
        </dependency>
        <!--打印日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.3.13.RELEASE</version>
 
        </dependency>
        <!-- mybatis分页插件 -->
        <!-- <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId>
            <version>4.1.4</version> </dependency> -->     <!-- javax.servlet.jsp-api -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
 
        <!-- spring-web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
 
        <!-- spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <!-- 阿里巴巴fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>
        <!--dbcp -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.1.1</version>
        </dependency>
 
        <!-- 辅助 -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.4</version>
        </dependency>
 
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>
 
        <!-- end -->
 
 
    </dependencies>
    <build>
 
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
 
        </plugins>
    </build>
</project>
 

1.5.2 建立实体类和相应的mapper以及xml

(1)编写POJO

package cn.entity;
 
public class User {
 
     
    private Integer Id;
    private String userName;
    private String PassWord;
    public Integer getId() {
        return Id;
    }
    public void setId(Integer id) {
        Id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassWord() {
        return PassWord;
    }
    public void setPassWord(String passWord) {
        PassWord = passWord;
    }
     
     
}
 

(2)编写数据访问层接口

package cn.mapper;

import cn.entity.User;

public interface UserMapper {

    
    public User selectUserInfo(String userName);
}

(3)编写XML配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mapper.UserMapper">

<select id="selectUserInfo" parameterType="String" resultType="User">
select * from `user` where userName=#{userName}

</select>

</mapper>

1.5.3 建立service以及service的实现类

(1)编写业务接口

package cn.service;

import cn.entity.User;

public interface UserService {

    
    public User selectUserInfo(String userName);
}

(2)编写业务接口实现类

package cn.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.entity.User;
import cn.mapper.UserMapper;
import cn.service.UserService;

@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public User selectUserInfo(String userName) {
        // TODO Auto-generated method stub
        return userMapper.selectUserInfo(userName);
    }

}

1.5.4 编写mybatis整合spring相关的配置文件

application-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:ehcache="http://www.springmodules.org/schema/ehcache"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd  
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd  
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd  
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd  
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd  
        http://www.springmodules.org/schema/ehcache http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd">

    <!-- 自动扫描(自动注入) -->
    <context:component-scan base-package="cn.service" />
    
    <!-- 配置连接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="1234"></property>
    </bean>


    <!-- myBatis文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
        <property name="configLocation" value="classpath:mybatis-config.xml" />
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.mapper" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
    <!-- 配置事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 配置事物的注解方式注入 -->
    <tx:annotation-driven transaction-manager="transactionManager" />


</beans>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <!-- 给cn.entity包下起别名 -->
        <package name="cn.entity" />

    </typeAliases>

</configuration>

log4j.properties

#config root logger
log4j.rootLogger = INFO,system.out
log4j.appender.system.out=org.apache.log4j.ConsoleAppender
log4j.appender.system.out.layout=org.apache.log4j.PatternLayout
log4j.appender.system.out.layout.ConversionPattern=[Log] %5p[%F:%L]:%m%n

#config this Project.file logger
log4j.logger.thisProject.file=INFO,thisProject.file.out
log4j.appender.thisProject.file.out=org.apache.log4j.DailyRollingFileAppender
log4j.appender.thisProject.file.out.File=logContentFile.log
log4j.appender.thisProject.file.out.layout=org.apache.log4j.PatternLayout

1.5.5 单元测试确保application-config.xml和mybatis-config.xml配置正确

package cn.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.entity.User;
import cn.mapper.UserMapper;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-config.xml")
public class JunitTest {

    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    public void testName() throws Exception {
        
        User user = userMapper.selectUserInfo("youcong");
        System.out.println(user.getPassWord());
        
    }
}

运行出来输出对应的信息,说明配置没有问题,同时也说明的MyBatis成功整合Spring。

1.6 小结

通过本章的学习,我相信你对Spring会有一个大致的了解,另外本章动手实践的只有一个MyBatis整合Spring的例子,其实本章可以有很多例子,笔者因为时间缘故就没有写太多,当然了,还考虑到本章肯定会非常长,所以例子也没有写太多,不过读者请放心,后续会有更多的补充,这种更多的补充,不仅对读者有帮助,同时对笔者个人也有帮助。

本文部分内容来自:笔者博客园

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 38 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-10-26
个人主页被 1.4k 人浏览