个人整理·Java开发工程师·常用功能

1. JWT令牌

1. 编写JWT工具类

        <!--JWT令牌-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

public class JWTUtils {

    private static final String jwtToken = "123456Mszlu!@#$$";

    public static String createToken(Long userId){
        Map<String,Object> claims = new HashMap<>();
        claims.put("userId",userId);
        JwtBuilder jwtBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
                .setClaims(claims) // body数据,要唯一,自行设置
                .setIssuedAt(new Date()) // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// 一天的有效时间
        String token = jwtBuilder.compact();
        return token;
    }

    public static Map<String, Object> checkToken(String token){
        try {
            Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
            return (Map<String, Object>) parse.getBody();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;

    }

    //测试用
    public static void main(String[] args) {
        BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
        //加密所需的salt
        textEncryptor.setPassword("mszlu_blog_$#@wzb_&^%$#");
        //要加密的数据(数据库的用户名或密码)
        String username = textEncryptor.encrypt("root");
        String password = textEncryptor.encrypt("root");
        System.out.println("username:"+username);
        System.out.println("password:"+password);
    }

}

2. 业务层登录注册退出案例  

@Service
@Transactional
public class LoginServiceImpl implements LoginService {

    @Autowired
    private SysUserService sysUserService; 
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    private static final String slat = "mszlu!@#";

    @Override
    public Result login(LoginParam loginParam) {
        /**
         * 1. 检查参数是否合法
         * 2. 根据用户名和密码去user表中查询 是否存在
         * 3. 如果不存在 登录失败
         * 4. 如果存在 ,使用jwt 生成token 返回给前端
         * 5. token放入redis当中,redis  token:user信息 设置过期时间
         *  (登录认证的时候 先认证token字符串是否合法,去redis认证是否存在)
         */
        String account = loginParam.getAccount();
        String password = loginParam.getPassword();
        if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){
            return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
        }
        password = DigestUtils.md5Hex(password + slat);
        SysUser sysUser = sysUserService.findUser(account,password);
        if (sysUser == null){
            return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
        }
        String token = JWTUtils.createToken(sysUser.getId());

        redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
        return Result.success(token);
    }

    //取出tocken
    @Override
    public SysUser checkToken(String token) {
        if (StringUtils.isBlank(token)){
            return null;
        }
        Map<String, Object> stringObjectMap = JWTUtils.checkToken(token);
        if (stringObjectMap == null){
            return null;
        }
        String userJson = redisTemplate.opsForValue().get("TOKEN_" + token);
        if (StringUtils.isBlank(userJson)){
            return null;
        }
        SysUser sysUser = JSON.parseObject(userJson, SysUser.class);
        return sysUser;
    }

    @Override
    public Result logout(String token) {
        redisTemplate.delete("TOKEN_"+token);
        return Result.success(null);
    }

    @Override
    public Result register(LoginParam loginParam) {
        /**
         * 1. 判断参数 是否合法
         * 2. 判断账户是否存在,存在 返回账户已经被注册
         * 3. 不存在,注册用户
         * 4. 生成token
         * 5. 存入redis 并返回
         * 6. 注意 加上事务,一旦中间的任何过程出现问题,注册的用户 需要回滚
         */
        String account = loginParam.getAccount();
        String password = loginParam.getPassword();
        String nickname = loginParam.getNickname();
        if (StringUtils.isBlank(account)
                || StringUtils.isBlank(password)
                || StringUtils.isBlank(nickname)
        ){
            return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
        }
        SysUser sysUser =  sysUserService.findUserByAccount(account);
        if (sysUser != null){
            return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),"账户已经被注册了");
        }
        sysUser = new SysUser();
        sysUser.setNickname(nickname);
        sysUser.setAccount(account);
        sysUser.setPassword(DigestUtils.md5Hex(password+slat));
        sysUser.setCreateDate(System.currentTimeMillis());
        sysUser.setLastLogin(System.currentTimeMillis());
        sysUser.setAvatar("/static/img/logo.b3a48c0.png");
        sysUser.setAdmin(1); //1 为true
        sysUser.setDeleted(0); // 0 为false
        sysUser.setSalt("");
        sysUser.setStatus("");
        sysUser.setEmail("");
        this.sysUserService.save(sysUser);

        String token = JWTUtils.createToken(sysUser.getId());

        redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
        return Result.success(token);
    }

}

2. 自定义注解

一般写在 handle 包下进行拦截处理

//对加了@Controller注解的方法进行拦截处理 AOP的实现
@ControllerAdvice
public class AllExceptionHandler {
    //进行异常处理,处理Exception.class的异常
    //这个是拦截所有异常
    @ExceptionHandler(Exception.class)
    @ResponseBody //返回json数据
    public Result doException(Exception ex){
        ex.printStackTrace();
        return Result.fail(-999,"系统异常");
    }

}

5. 拦截器

1. 基本实现

应用场景:当某个资源我们每次请求都需要访问时,可以利用拦截器。

 1. 拦截器实现

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private LoginService loginService;

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

        if (!(handler instanceof HandlerMethod)){
            return true;
        }
        String token = request.getHeader("Authorization");
        log.info("=================request start===========================");
        String requestURI = request.getRequestURI();
        log.info("request uri:{}",requestURI);
        log.info("request method:{}",request.getMethod());
        log.info("token:{}", token);
        log.info("=================request end===========================");

        if (token == null){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        SysUser sysUser = loginService.checkToken(token);
        if (sysUser == null){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        //是登录状态,放行

        return true;
    }
}

2. 拦截器生效 

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //跨域配置
        registry.addMapping("/**").allowedOrigins("http://localhost:8080");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/test");
    }
}

2. 微服务中的实现-GateWay网关实现过滤器功能

规定对符合某种规则的的路径,进行一系列拦截处理

下面以一个 验证登陆状态 作为案例

1. 服务集成Redis

1. 依赖

2. 配置

3. 序列化器

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

spring:
  data:
    redis:
      host: localhost
      port: 6379 

@Configuration
public class RedisConfig {

    @Bean
    @Primary
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        //String的序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        //序列号key value
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

2. gateway模块添加全局Filter,统一处理登录

/**
 * 全局Filter,统一处理会员登录
 */
@Slf4j
@Component
// GlobalFilter: GateWay里面的,通过它实现全局过滤器
// Ordered: 设置过滤器优先级
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private RedisTemplate<String , String> redisTemplate;

    // 用来存储访问路径
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        log.info("path {}", path);
        
        // 封装了一个方法,验证登录状态
        UserInfo userInfo = this.getUserInfo(request);
        //api接口,异步请求,与path会匹配,校验用户必须登录
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            if(null == userInfo) { // 没有登陆,获取response 向前端发消息
                ServerHttpResponse response = exchange.getResponse();
                return out(response, ResultCodeEnum.LOGIN_AUTH);
            }
        }
        
        // 方行的代码
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() { //优先级
        return 0;
    }

    // 给一个response和一个消息,单纯的向前端发送一个错误信息
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        Result result = Result.build(null, resultCodeEnum);
        byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
    
    // 主要完成1.获取token 2.向Redis里查此token 3.查到转为实体类
    private UserInfo getUserInfo(ServerHttpRequest request) {
        String token = "";
        List<String> tokenList = request.getHeaders().get("token");
        if(null  != tokenList) {
            token = tokenList.get(0);
        }
        if(!StringUtils.isEmpty(token)) {
            String userInfoJSON = redisTemplate.opsForValue().get("user:spzx:"+token);
            if(StringUtils.isEmpty(userInfoJSON)) {
                return null ;
            }else {
                return JSON.parseObject(userInfoJSON , UserInfo.class) ;
            }
        }
        return null;
    }
}

4. Redis缓存与Spring Cache框架

1. Spring Cache注解介绍

注解说明
@EnableCaching开启缓存注解功能
@Cacheable在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中
@CachePut将方法的返回值放到缓存中
@CacheEvict将一条或多条数据从缓存中删除

1. 加入Redis起步依赖与 Spring Cache

<!-- redis的起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

 2. 加入Redis配置信息

spring:
  # Redis的相关配置
  data:
    redis:
      host: localhost
      port: 6379

3. 配置Redis序列化器

import org.springframework.cache.CacheManager;

@Configuration
public class RedisConfig {

    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory connectionFactory) {

        //定义序列化器
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                //过期时间600秒
                .entryTtl(Duration.ofSeconds(600))
                // 配置序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer));

        RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .build();

        return cacheManager;
    }

}

4. 启动类加入

@EnableCaching

5. 使用

// 查询数据先看Redis中是否有数据 
// 将查询到的数据保存至Redis,key为 userCache::userId
@Cacheable(value = "userCache" , key = "#userId")
public User findById(Long userId) {
    log.info("用户数据查询成功...");
    User user = new User() ;
    user.setAge(23);
    user.setUserName("xz");
    return user;
}

// 将插入的数据保存至Redis;使用返回数据user的username作为key
@CachePut(value = "userCache", key = "#user.userName")
public User saveUser(User user) {
    log.info("用户数据保存成功...");
    return user ;
}

// 删除数据同时将Redis数据删除
@CacheEvict(value = "userCache" , key = "#userId")
public void deleteById(Long userId) {
    log.info("用户数据删除成功...");
}

6. 不带Spring Cache的使用方法

在每一个ServiceImpl中注入RedisTemplate

@Autowired
private RedisTemplate<String , String> redisTemplate ;

@Override
public List<Category> findOneCategory() {

    // 利用key查找json字符串
    String categoryListJSON = redisTemplate.opsForValue().get("category:one");
    // 如果不是空,返回实体类数据
    if(!StringUtils.isEmpty(categoryListJSON)) {
        // 转换Json字符串
        List<Category> categoryList = JSON.parseArray(categoryListJSON, Category.class);
        return categoryList ;
    }
    
    // 如果是空,则需要查询数据库
    List<Category> categoryList = categoryMapper.findOneCategory();
    // 设置过期时间
    redisTemplate.opsForValue().set("category:one" , JSON.toJSONString(categoryList) , 7 , TimeUnit.DAYS);
    return categoryList ;
}

5. 图片服务器:七里云、Minon

· 七里云·代码示例

1. 需要前往 七里云-开发者中心 :https://developer.qiniu.com/,注册

2. 保存自己的key与password

2. 在 对象存储 - 空间管理 - 创建空间 - 选择地区:会生成测试域名

前端

export function upload(formdata) {

  return request({

    headers: {'Content-Type': 'multipart/form-data'},

    url: '/upload',

    method: 'post',

    data: formdata

  })

}

1. 导入依赖,配置文件

# 上传文件总的最大值
spring.servlet.multipart.max-request-size=20MB
# 单个文件的最大值
spring.servlet.multipart.max-file-size=2MB

<dependency>
  <groupId>com.qiniu</groupId>
  <artifactId>qiniu-java-sdk</artifactId>
  <version>[7.7.0, 7.7.99]</version>
</dependency> 

2. 书写七牛云工具类

@Component
public class QiniuUtils {
    //测试的域名,有时加http,有时不加
    public static  final String url = "***********.com/";

    private final String accessKey = "**********************";

    private final String accessSecretKey = "***********************";

    public boolean upload(MultipartFile file, String fileName){

        //构造一个带指定 Region 对象的配置类
// 选择自己对应的地区
        Configuration cfg = new Configuration(Region.huadong());
        //...其他参数参考类注释
        UploadManager uploadManager = new UploadManager(cfg);
        //...生成上传凭证,然后准备上传
        String bucket = "xz-spzx-project";
        //默认不指定key的情况下,以文件内容的hash值作为文件名
        try {
            byte[] uploadBytes = file.getBytes();
            Auth auth = Auth.create(accessKey, accessSecretKey);
            String upToken = auth.uploadToken(bucket);
            Response response = uploadManager.put(uploadBytes, fileName, upToken);
            //解析上传成功的结果
            DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }
}

3. controller接口书写

@RestController
@RequestMapping("/admin/system")
public class FileUploadController {

    @Autowired
    private FileUploadService fileUploadService;

    @Operation(summary = "图片上传")
    @PostMapping(value = "/fileUpload")
    public Result<String> fileUploadService(@RequestParam(value = "file") MultipartFile multipartFile) {
        String fileUrl = fileUploadService.fileUpload(multipartFile) ;
        return Result.build(fileUrl , ResultCodeEnum.SUCCESS);
    }

}

4. service层书写

    @Override
    public String fileUpload(MultipartFile multipartFile) {
        String fileName = UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(multipartFile.getOriginalFilename(), ".");
        boolean upload = qiniuUtils.upload(multipartFile, fileName);
        if (upload){
            System.out.println(QiniuUtils.url + fileName);
            return QiniuUtils.url + fileName;
        }
        return "500";
    }

· Minon

1. 官网下载minon.exe

放到一个目录里,此目录下创建data,既此目录有 data文件夹和minon.exe

2. 启动成功后,可以通过给出的端口,key,password进入客户端

1. 第一个9000端口是我们上传文件要写的接口

2. 第二个端口不定,是我们的客户端

3. 创建一个桶,起名字,设置为公共的public,创建accessKey和secreKey

3. java工具模块里引入依赖

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.2</version>
</dependency>

4. controller层

@RestController
@RequestMapping("/admin/system")
public class FileUploadController {

    @Autowired
    private FileUploadService fileUploadService ;

    @PostMapping(value = "/fileUpload")
    public Result<String> fileUploadService(@RequestParam(value = "file") MultipartFile multipartFile) {
        String fileUrl = fileUploadService.fileUpload(multipartFile) ;
        return Result.build(fileUrl , ResultCodeEnum.SUCCESS) ;
    }

}

5. service层

@Service
public class FileUploadServiceImpl implements FileUploadService {

    @Autowired
    private MinioProperties minioProperties ;

    @Override
    public String fileUpload(MultipartFile multipartFile) {

        try {
            // 创建一个Minio的客户端对象
            MinioClient minioClient = MinioClient.builder()
                    .endpoint(minioProperties.getEndpointUrl())
                    .credentials(minioProperties.getAccessKey(), minioProperties.getSecreKey())
                    .build();

            // 判断桶是否存在
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());
            if (!found) {       // 如果不存在,那么此时就创建一个新的桶
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());
            } else {  // 如果存在打印信息
                System.out.println("Bucket 'spzx-bucket' already exists.");
            }

            // 设置存储对象名称
            String dateDir = DateUtil.format(new Date(), "yyyyMMdd");
            String uuid = UUID.randomUUID().toString().replace("-", "");
            //20230801/443e1e772bef482c95be28704bec58a901.jpg
            String fileName = dateDir+"/"+uuid+multipartFile.getOriginalFilename();
            System.out.println(fileName);

            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(minioProperties.getBucketName())
                    .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                    .object(fileName)
                    .build();
            minioClient.putObject(putObjectArgs) ;

            return minioProperties.getEndpointUrl() + "/" + minioProperties.getBucketName() + "/" + fileName ;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

6. 利用定义资源类知识将下面四个数据加入yml

spzx:
  minio:
    endpointUrl:  http://169.254.40.162:9000
    accessKey: 
    secreKey: 
    bucketName: spzx-bucket

6. ThreadLocal获取当前用户·拦截器

ThreadLocal可以从一次 请求开始 到这次 请求结束 之间 完成数据共享

一般放在common模块工具类中

1. 基本实现

1. 创建工具类ThreadLocal类

public class ThreadLocalUtil {
    // 创建一个ThreadLocal对象
    private static final ThreadLocal<SysUser> threadLocal = new ThreadLocal<>() ;

    // 定义存储数据的静态方法
    public static void set(SysUser sysUser) {
        threadLocal.set(sysUser);
    }

    // 定义获取数据的方法
    public static SysUser get() {
        return threadLocal.get() ;
    }

    // 删除数据的方法
    public static void remove() {
        threadLocal.remove();
    }
}

2. 拦截器中去使用

使用ThreadLocal一定要在拦截器中关闭,否则会发生内存泄漏

@Component
public class LoginInterceptor implements HandlerInterceptor {//拦截器
    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    @Override //true就放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 获取请求方式
        String method = request.getMethod()
        if("OPTIONS".equals(method)) {      // 如果是跨域预检请求,直接放行
            return true ;
        }

        // 获取token
        String token = request.getHeader("token");
        String sysUserInfoJson = redisTemplate.opsForValue().get("user:login:" + token);
        //主要逻辑详见拦截器
        //主要逻辑详见拦截器
        //主要逻辑详见拦截器
   
        // 将用户数据存储到ThreadLocal中
        SysUser sysUser = JSON.parseObject(sysUserInfoJson, SysUser.class);
        ThreadLocalUtil.set(sysUser);

        // 延长Redis中的用户数据的有效时间
        // 只有真正退出本用户了,才会结束(保证本用户一直可以有效)
        redisTemplate.expire("user:login:" + token , 30 , TimeUnit.MINUTES) ;

        // 放行
        return true ;
    }

    @Override //请求结束,执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ThreadLocalUtil.remove();  // 移除threadLocal中的数据
    }
}

2. 微服务中实现

1. 引入依赖

<!-- spring boot web开发所需要的起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <scope>provided</scope>
</dependency>

2. 利用基本实现

这里比基础实现要简单些,因为一般而言,基础实现要完成登陆状态效验和本功能,但是微服务中网关会进行登录状态,因此就只需要简单的加入到线程类中去就好

public class UserLoginAuthInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

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

        // 如果token不为空,那么此时验证token的合法性
        String userInfoJSON = redisTemplate.opsForValue().get("user:spzx:" + request.getHeader("token"));
        ThreadLocalUtil.setUserInfo(JSON.parseObject(userInfoJSON , UserInfo.class));
        return true ;

    }

    @Override //请求结束,执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ThreadLocalUtil.remove();  // 移除threadLocal中的数据
    }

}

3. 创建自定义注解,将配置类和拦截器导入

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@Import(value = { UserLoginAuthInterceptor.class , UserWebMvcConfiguration.class})
public @interface EnableUserWebMvcConfiguration {
}

4. 加入到需要使用此功能的服务中的的启动类上

@EnableUserWebMvcConfiguration

7. 统一异常处理

1. 创建自定义异常类

可以建在 exception 包下

这个例子里 ResultCodeEnum 是将一些错误码错误消息封装起来了

@Data
public class GuiguException extends RuntimeException {

    private Integer code ;          // 错误状态码
    private String message ;        // 错误消息

    private ResultCodeEnum resultCodeEnum ;     // 封装错误状态码和错误消息

    public GuiguException(ResultCodeEnum resultCodeEnum) {
        this.resultCodeEnum = resultCodeEnum ;
        this.code = resultCodeEnum.getCode() ;
        this.message = resultCodeEnum.getMessage();
    }

    public GuiguException(Integer code , String message) {
        this.code = code ;
        this.message = message ;
    }
}

2. 创建统一异常处理类 

一般建在公共模块的 handle 包下

/**
 * 统一异常处理类
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    //所有异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result error(Exception e){
        e.printStackTrace();
        return Result.build(null , 201,"出现了异常") ;
    }

    //自定义异常
    @ExceptionHandler(value = GuiguException.class)   
    @ResponseBody
    public Result error(GuiguException exception) {
        exception.printStackTrace();
        return Result.build(null , exception.getResultCodeEnum()) ;
    }
}

3. 找地方抛出这个错误

if(sysUser == null) {
    throw new GuiguException(ResultCodeEnum.LOGIN_ERROR);
}

 4. ResultCodeEnum封装的如下

@Getter // 提供获取属性值的getter方法
public enum ResultCodeEnum {

    SUCCESS(200 , "操作成功") ,
    LOGIN_ERROR(201 , "用户名或者密码错误"),
    VALIDATECODE_ERROR(202 , "验证码错误") ,
    LOGIN_AUTH(208 , "用户未登录"),
    USER_NAME_IS_EXISTS(209 , "用户名已经存在"),
    SYSTEM_ERROR(9999 , "您的网络有问题请稍后重试"),
    NODE_ERROR( 217, "该节点下有子节点,不可以删除"),
    DATA_ERROR(204, "数据异常"),
    ACCOUNT_STOP( 216, "账号已停用"),

    STOCK_LESS( 219, "库存不足"),

    ;

    private Integer code ;      // 业务状态码
    private String message ;    // 响应消息

    private ResultCodeEnum(Integer code , String message) {
        this.code = code ;
        this.message = message ;
    }

}

8. Security权限管理

所谓权限控制,就是为各个角色分配不同权限,使用Security会更加安全。

要有权限表(包含了权限id,权限路径)、角色与权限对应表

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

1. 配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();//彩虹表加密策略,md5并不安全
    }

    public static void main(String[] args) {
        //加密策略 彩虹表  
        String mszlu = new BCryptPasswordEncoder().encode("mszlu");
        System.out.println(mszlu);
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests() //开启登录认证
//                .antMatchers("/user/findAll").hasRole("admin") //访此接口需要admin的角色
                .antMatchers("/css/**").permitAll()//所有都可以
                .antMatchers("/img/**").permitAll()
                .antMatchers("/js/**").permitAll()
                .antMatchers("/plugins/**").permitAll()
                .antMatchers("/admin/**").access("@authService.auth(request,authentication)") 
//@authService表示我们需要自定义一个service 来去实现实时的权限认证
                .antMatchers("/pages/**").authenticated()
                .and().formLogin()
                .loginPage("/login.html") //自定义的登录页面
                .loginProcessingUrl("/login") //登录处理接口
                    //前端name为username和password
                .usernameParameter("username") //定义登录时的用户名的key 默认为username
                .passwordParameter("password") //定义登录时的密码key,默认是password
                .defaultSuccessUrl("/pages/main.html")
                .failureUrl("/login.html")
                .permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过
                .and().logout() //退出登录配置
                .logoutUrl("/logout") //退出登录接口
                .logoutSuccessUrl("/login.html")
                .permitAll() //退出登录的接口放行
                .and()
                .httpBasic()
                .and()
                .csrf().disable() //csrf关闭 如果是自定义登录页面 需要关闭
                .headers().frameOptions().sameOrigin();
    }
}

 2. 将登录的信息交给spring security

继承UserDetailsService,重写方法loadUserByUsername

判断数据库中是否有username这个用户,有的话将账号密码等交给spring security

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.GrantedAuthority;
@Component
@Slf4j
public class SecurityUserService implements UserDetailsService {
    @Autowired
    private AdminService adminService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username:{}",username);
        //当用户登录的时候,springSecurity 就会将请求 转发到此
        //根据用户名 查找用户,不存在 抛出异常,存在 将用户名,密码,授权列表 组装成springSecurity的User对象 并返回
        Admin adminUser = adminService.findAdminByUserName(username);
        if (adminUser == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        ArrayList<GrantedAuthority> authorities = new ArrayList<>();
        UserDetails userDetails = new User(username,adminUser.getPassword(), authorities);
        //剩下的认证 就由框架帮我们完成
        return userDetails;
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }
}

3.  查询用户是否存在

普通的查询数据库是否有username这个用户

@Service
public class AdminService {

    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private PermissionMapper permissionMapper;

    public Admin findAdminByUserName(String username){
        LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Admin::getUsername,username).last("limit 1");
        Admin adminUser = adminMapper.selectOne(queryWrapper);
        return adminUser;
    }

    public List<Permission> findPermissionsByAdminId(Long adminId){
        return permissionMapper.findPermissionsByAdminId(adminId);
    }

}

public interface AdminMapper extends BaseMapper<Admin> {
}

public interface PermissionMapper extends BaseMapper<Permission> {

    List<Permission> findPermissionsByAdminId(Long adminId);
}

4. 自定义的service

主要写逻辑的地点

查询到的已经登录的用户,需要通过查询它的权限表,从而给出对应的权限

@Service
@Slf4j
public class AuthService {

    @Autowired
    private AdminService adminService;

    public boolean auth(HttpServletRequest request, Authentication authentication){
        String requestURI = request.getRequestURI();
        log.info("request url:{}", requestURI);
        //true代表放行 false 代表拦截
        Object principal = authentication.getPrincipal();
        // anonymousUser 表示游客
        if (principal == null || "anonymousUser".equals(principal)){
            //未登录
            return false;
        }
        UserDetails userDetails = (UserDetails) principal;
        String username = userDetails.getUsername();
        Admin admin = adminService.findAdminByUserName(username);
        if (admin == null){
            return false;
        }
        if (admin.getId() == 1){
            //认为是超级管理员
            return true;
        }
        List<Permission> permissions = adminService.findPermissionsByAdminId(admin.getId());
        // 先去除请求的路径中可能存在的 ?
        requestURI = StringUtils.split(requestURI,'?')[0];
        // 相匹配成功的权限才会权限通过
        for (Permission permission : permissions) {
            //权限表中路径字段是否与发出的路径一致呢
            if (requestURI.equals(permission.getPath())){
                log.info("权限通过");
                return true;
            }
        }
        return false;
    }
}

9. 利用ObjectMapper类解决JS无法识别17位及以上的数值

//防止前端 精度损失 把id转为string
// 分布式id 比较长,传到前端 会有精度损失,必须转为string类型 进行传输,就不会有问题了
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

10. mybatis-plus提供的字段自动填充

 11. AOP实现自定义注解+Log日志打印

通过创建common-log模块,实现Log日志

需要使用的依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <scope>provided</scope>
    </dependency>

1. 自定义Log注解

//此注解是放到方法上的注解
@Target({ElementType.METHOD}) 
//此注解在运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {		

    //自定义注解的一些属性,设置一些默认值

    // 模块名称,比如:"订单模块"
    public String title();
    // 自定义的操作人类型枚举类								
    public OperatorType operatorType() default OperatorType.MANAGE;
    // 业务类型(0其它 1新增 2修改 3删除)
    public int businessType() ;     
    // 是否保存请求的参数
    public boolean isSaveRequestData() default true;   
    // 是否保存响应的参数
    public boolean isSaveResponseData() default true;  
    
}

自定义的操作人类型枚举类    

public enum OperatorType {		// 操作人类别
    OTHER,		// 其他
    MANAGE,		// 后台用户
    MOBILE		// 手机端用户
}

2. 定义切面类

@Aspect //定义切面
@Component //交给spring管理
@Slf4j //日志
public class LogAspect {      

    //注入service,注意:他的实现类可以不在此模块下
    @Autowired
    private AsyncOperLogService asyncOperLogService ;      

    // 环绕通知定义
    @Around(value = "@annotation(sysLog)")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {
        
        //业务执行前代码,构建前置参数
        //SysOperLog 对应数据库信息
        SysOperLog sysOperLog = new SysOperLog() ;
        //自定义工具类将SysOperLog 不断完善信息
        LogUtil.beforeHandleLog(sysLog , joinPoint , sysOperLog) ;

        Object proceed = null;
        try {  // 代码执行进入到catch中,业务方法执行产生异常
            proceed = joinPoint.proceed(); // 执行业务方法    

            //业务成功后执行的代码
            //自定义工具类将SysOperLog 不断完善信息
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 0 , null) ;
         
        } catch (Throwable e) { 

            //业务失败后执行的代码
            e.printStackTrace();  // 打印异常信息
            //自定义工具类将SysOperLog 不断完善信息
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 1 , e.getMessage());
            //切记抛出异常!!!,否则事务失效
            //原因:事务也基于aop实现且优先级高于自定义aop                       
            throw new RuntimeException();
        }

        //这里可以写将返回结果保存至数据库
        asyncOperLogService.saveSysOperLog(sysOperLog);        

        return proceed ; // 返回执行结果
    }
}

例子:工具类和实体类

@Data
public class SysOperLog extends BaseEntity {

	private String title;					// 模块标题
	private String method;					// 方法名称
	private String requestMethod;			// 请求方式
	private String operatorType;			// 操作类别(0其它 1后台用户 2手机端用户)
    private Integer businessType ;			// 业务类型(0其它 1新增 2修改 3删除)
	private String operName;				// 操作人员
	private String operUrl;					// 请求URL
	private String operIp;					// 主机地址
	private String operParam;				// 请求参数
	private String jsonResult;				// 返回参数
	private Integer status;					// 操作状态(0正常 1异常)
	private String errorMsg;				// 错误消息

}
public class LogUtil {

    //操作执行之后调用
    public static void afterHandlLog(Log sysLog, Object proceed,
                                     SysOperLog sysOperLog, int status ,
                                     String errorMsg) {
        //判断注解是否需要执行后参数
        if(sysLog.isSaveResponseData()) {
            sysOperLog.setJsonResult(JSON.toJSONString(proceed));
        }
        //设置状态
        sysOperLog.setStatus(status);
        //设置错误信息
        sysOperLog.setErrorMsg(errorMsg);
    }

    //操作执行之前调用
    public static void beforeHandleLog(Log sysLog,
                                       ProceedingJoinPoint joinPoint,
                                       SysOperLog sysOperLog) {

        // 设置操作模块名称
        sysOperLog.setTitle(sysLog.title());
        //设置操作人类型
        sysOperLog.setOperatorType(sysLog.operatorType().name());

        // 获取目标方法信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature() ;
        Method method = methodSignature.getMethod();
        // 设置方法名称
        sysOperLog.setMethod(method.getDeclaringClass().getName());

        // 获取请求相关参数
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //设置请求方法
        sysOperLog.setRequestMethod(request.getMethod());
        //设置URL信息
        sysOperLog.setOperUrl(request.getRequestURI());
        //设置IP信息
        sysOperLog.setOperIp(request.getRemoteAddr());

        // 判断是否需要执行后参数,设置请求参数
        if(sysLog.isSaveRequestData()) {
            String requestMethod = sysOperLog.getRequestMethod();
            if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
                String params = Arrays.toString(joinPoint.getArgs());
                sysOperLog.setOperParam(params);
            }
        }
        sysOperLog.setOperName(AuthContextUtil.get().getUserName());
    }
}

3. 通过自定义注解让Spring Boot可以扫描到

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
// 通过Import注解导入日志切面类到Spring容器中
@Import(value = LogAspect.class)           
public @interface EnableLogAspect {
}

4. 在主服务里的启动类上加上

@EnableLogAspect

5. 测试使用

@Log(title = "角色添加",businessType = 0) //添加Log注解,设置属性
@PostMapping(value = "/saveSysRole")
public Result saveSysRole(@RequestBody SysRole SysRole) {
    sysRoleService.saveSysRole(SysRole) ;
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}

 11. 阿里云短信服务

1. 前往 阿里云 - 云市场 - 短信服务

找到一个服务点开后,点击使用即可,我使用的是-【三网106短信】

2. 进入 阿里云管控中心查看AppCode

3. 复制Java代码的请求示例

	public static void main(String[] args) {
	    String host = "https://gyytz.market.alicloudapi.com";
	    String path = "/sms/smsSend";
	    String method = "POST";
	    String appcode = "你自己的AppCode";
	    Map<String, String> headers = new HashMap<String, String>();
	   //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
	    headers.put("Authorization", "APPCODE " + appcode);
	    Map<String, String> querys = new HashMap<String, String>();
	    querys.put("mobile", "mobile"); //手机号
	    querys.put("param", "**code**:12345,**minute**:5"); //短信验证码

//smsSignId(短信前缀)和templateId(短信模板),可登录国阳云控制台自助申请。参考文档:http://help.guoyangyun.com/Problem/Qm.html

	    querys.put("smsSignId", "2e65b1bb3d054466b82f0c9d125465e2"); //短信前缀id
	    querys.put("templateId", "908e94ccf08b4476ba6c876d13f084ad"); //模板id
	    Map<String, String> bodys = new HashMap<String, String>();


	    try {
	    	/**
	    	* 重要提示如下:
	    	* HttpUtils请从\r\n\t    \t* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java\r\n\t    \t* 下载
	    	*
	    	* 相应的依赖请参照
	    	* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
	    	*/
	    	HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
	    	System.out.println(response.toString());
	    	//获取response的body
	    	//System.out.println(EntityUtils.toString(response.getEntity()));
	    } catch (Exception e) {
	    	e.printStackTrace();
	    }
	}

4. 固定的工具类与导入依赖

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2.1</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.2.1</version>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-util</artifactId>
    <version>9.3.7.v20160115</version>
</dependency>
import org.apache.http.HttpResponse;
import org.apache.commons.lang3.StringUtils;
import java.security.cert.X509Certificate;

public class HttpUtils {
	
	/**
	 * get
	 * 
	 * @param host
	 * @param path
	 * @param method
	 * @param headers
	 * @param querys
	 * @return
	 * @throws Exception
	 */
	public static HttpResponse doGet(String host, String path, String method, 
			Map<String, String> headers, 
			Map<String, String> querys)
            throws Exception {    	
    	HttpClient httpClient = wrapClient(host);

    	HttpGet request = new HttpGet(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
        	request.addHeader(e.getKey(), e.getValue());
        }
        
        return httpClient.execute(request);
    }
	
	/**
	 * post form
	 * 
	 * @param host
	 * @param path
	 * @param method
	 * @param headers
	 * @param querys
	 * @param bodys
	 * @return
	 * @throws Exception
	 */
	public static HttpResponse doPost(String host, String path, String method, 
			Map<String, String> headers, 
			Map<String, String> querys, 
			Map<String, String> bodys)
            throws Exception {    	
    	HttpClient httpClient = wrapClient(host);

    	HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
        	request.addHeader(e.getKey(), e.getValue());
        }

        if (bodys != null) {
            List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();

            for (String key : bodys.keySet()) {
                nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
            request.setEntity(formEntity);
        }

        return httpClient.execute(request);
    }	
	
	/**
	 * Post String
	 * 
	 * @param host
	 * @param path
	 * @param method
	 * @param headers
	 * @param querys
	 * @param body
	 * @return
	 * @throws Exception
	 */
	public static HttpResponse doPost(String host, String path, String method, 
			Map<String, String> headers, 
			Map<String, String> querys, 
			String body)
            throws Exception {    	
    	HttpClient httpClient = wrapClient(host);

    	HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
        	request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
        	request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }
	
	/**
	 * Post stream
	 * 
	 * @param host
	 * @param path
	 * @param method
	 * @param headers
	 * @param querys
	 * @param body
	 * @return
	 * @throws Exception
	 */
	public static HttpResponse doPost(String host, String path, String method, 
			Map<String, String> headers, 
			Map<String, String> querys, 
			byte[] body)
            throws Exception {    	
    	HttpClient httpClient = wrapClient(host);

    	HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
        	request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
        	request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }
	
	/**
	 * Put String
	 * @param host
	 * @param path
	 * @param method
	 * @param headers
	 * @param querys
	 * @param body
	 * @return
	 * @throws Exception
	 */
	public static HttpResponse doPut(String host, String path, String method, 
			Map<String, String> headers, 
			Map<String, String> querys, 
			String body)
            throws Exception {    	
    	HttpClient httpClient = wrapClient(host);

    	HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
        	request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
        	request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }
	
	/**
	 * Put stream
	 * @param host
	 * @param path
	 * @param method
	 * @param headers
	 * @param querys
	 * @param body
	 * @return
	 * @throws Exception
	 */
	public static HttpResponse doPut(String host, String path, String method, 
			Map<String, String> headers, 
			Map<String, String> querys, 
			byte[] body)
            throws Exception {    	
    	HttpClient httpClient = wrapClient(host);

    	HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
        	request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
        	request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }
	
	/**
	 * Delete
	 *  
	 * @param host
	 * @param path
	 * @param method
	 * @param headers
	 * @param querys
	 * @return
	 * @throws Exception
	 */
	public static HttpResponse doDelete(String host, String path, String method, 
			Map<String, String> headers, 
			Map<String, String> querys)
            throws Exception {    	
    	HttpClient httpClient = wrapClient(host);

    	HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
        	request.addHeader(e.getKey(), e.getValue());
        }
        
        return httpClient.execute(request);
    }
	
	private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
    	StringBuilder sbUrl = new StringBuilder();
    	sbUrl.append(host);
    	if (!StringUtils.isBlank(path)) {
    		sbUrl.append(path);
        }
    	if (null != querys) {
    		StringBuilder sbQuery = new StringBuilder();
        	for (Map.Entry<String, String> query : querys.entrySet()) {
        		if (0 < sbQuery.length()) {
        			sbQuery.append("&");
        		}
        		if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
        			sbQuery.append(query.getValue());
                }
        		if (!StringUtils.isBlank(query.getKey())) {
        			sbQuery.append(query.getKey());
        			if (!StringUtils.isBlank(query.getValue())) {
        				sbQuery.append("=");
        				sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
        			}        			
                }
        	}
        	if (0 < sbQuery.length()) {
        		sbUrl.append("?").append(sbQuery);
        	}
        }
    	
    	return sbUrl.toString();
    }
	
	private static HttpClient wrapClient(String host) {
		HttpClient httpClient = new DefaultHttpClient();
		if (host.startsWith("https://")) {
			sslClient(httpClient);
		}
		
		return httpClient;
	}
	
	private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] xcs, String str) {
                	
                }
                public void checkServerTrusted(X509Certificate[] xcs, String str) {
                	
                }
            };
            ctx.init(null, new TrustManager[] { tm }, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
        	throw new RuntimeException(ex);
        }
    }
}

4. 向自己代码中改造请求示例

注意:根据模板自行改造,不一样的服务会有不一样的代码

修改如下:

1. AppCode

2. 模板ID:template_id;短信前缀:smsSignId

3. 手机号、验证码等

@Service
public class SmsServiceImpl implements SmsService {

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    @Override
    public void sendValidateCode(String phone) {

        // 为了测试使用,不用每次都发短信
        String code = redisTemplate.opsForValue().get("phone:code:" + phone);
        if(StringUtils.hasText(code)) {
            return;
        }
        // 生成验证码
        String validateCode = RandomStringUtils.randomNumeric(4);
        // 过期时间
        redisTemplate.opsForValue().set("phone:code:" + phone , validateCode , 5 , TimeUnit.MINUTES);
        sendSms(phone , validateCode) ;
    }

    // 发送短信方法
    public void sendSms(String phone, String validateCode) {
        String host = "https://gyytz.market.alicloudapi.com";
        String path = "/sms/smsSend";
        String method = "POST";
        String appcode = "****************************";
        Map<String, String> headers = new HashMap<String, String>();
        //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
        headers.put("Authorization", "APPCODE " + appcode);
        Map<String, String> querys = new HashMap<String, String>();
        querys.put("mobile", phone);
        querys.put("param", "**code**:"+validateCode+",**minute**:5");

//smsSignId(短信前缀)和templateId(短信模板),可登录国阳云控制台自助申请。参考文档:http://help.guoyangyun.com/Problem/Qm.html

        querys.put("smsSignId", "2e65b1bb3d054466b82f0c9d125465e2");
        querys.put("templateId", "908e94ccf08b4476ba6c876d13f084ad");
        Map<String, String> bodys = new HashMap<String, String>();


        try {
            /**
             * 重要提示如下:
             * HttpUtils请从\r\n\t    \t* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java\r\n\t    \t* 下载
             *
             * 相应的依赖请参照
             * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
             */
            HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
            System.out.println(response.toString());
            //获取response的body
            //System.out.println(EntityUtils.toString(response.getEntity()));
        } catch (Exception e) {
            e.printStackTrace();
            throw new XZException(ResultCodeEnum.SYSTEM_ERROR);
        }
    }
}

12. 线程池技术优化查询、更新操作并行

13. 关于spring boot、spring cloud注解

14. MySql主从复制优化

15. Mybatis-puls常用方法

16. Elements plus中树形数据的处理方式

Elements plus里数据里,有child格式数据,属于数据里的数据,此时

17. 跨域问题

由于从8000端口想8001端口发送会出现跨域问题,需要配置类解决它

@Configuration
public class WebConfig  implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //跨域配置,不可设置为*,不安全, 前后端分离项目,可能域名不一致
        //本地测试 端口不一致 也算跨域
        registry.addMapping("/**").allowedOrigins("http://localhost:8080");
    }
}

18. 验证码图片生成

用hutool工具包中的工具类生成图片验证码

19. mybatis与sql的妙用

1. mybatis的通过集合批量插入

利用 <foreach> 插入多条语句

    <insert id="doAssign">
        insert into sys_role_menu (
        role_id,
        menu_id,
        create_time , update_time , is_deleted , is_half
        ) values
        <foreach collection="menuIdList" item="menuInfo" separator=",">
            (#{roleId} , #{menuInfo.id} , now() , now() , 0 , #{menuInfo.isHalf})
        </foreach>
    </insert>

2. sql语句多表查询-left join table_name on ***.id=***.id 

category_brand 联合 category 表,brand表,根据category_brand中两个 id 值取他们的与之对应的数据。

  • 当一个 id 有多个对应的数据会取下面的数据
  • 多表查询,所有列名都要用表名去 点 .
    <sql id="where">
        <where>
            <if test="categoryId != null and categoryId != ''">
                and cb.category_id = #{categoryId}
            </if>
            <if test="brandId != null and brandId != ''">
                and cb.brand_id = #{brandId}
            </if>
            and cb.is_deleted = 0
        </where>
    </sql>

    <select id="findByPage" resultType="product.CategoryBrand">
        select
            cb.id,cb.brand_id,cb.category_id,cb.create_time,cb.update_time,
            c.name as categoryName,
            b.name as brandName, b.logo
        from category_brand cb
        left join category c on c.id = cb.category_id
        left join brand b on b.id = cb.brand_id
        <include refid="where" />
        order by cb.update_time desc
    </select>

20. 开源框架EasyExcel的使用

目的:实现在页面上下载数据导出至本地文件,从本地文件导入至数据库。

1. 导出实现

1.1 添加依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.0</version>
</dependency>

1.2 定义一个实体类来封装excel每一行的数据

1.3 Controller层下载数据需要用到HttpServletResponse:

@GetMapping(value = "/exportData")
public void exportData(HttpServletResponse response) {
    categoryService.exportData(response);
}

1.4 Service层必须定义头部信息,通过write写

public void exportData(HttpServletResponse response) {

    try {

        // 设置响应结果类型 
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");

        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("分类数据", "UTF-8");
        //完全固定格式
        response.setHeader("Content-disposition",
 "attachment;filename=" + fileName + ".xlsx");
        //response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");

        // 查询数据库中的数据
        List<Category> categoryList = categoryMapper.selectAll();
        List<CategoryExcelVo> categoryExcelVoList = new ArrayList<>(categoryList.size());
        
        //省略 Category 转 CategoryExcelVo

        // 写出数据到浏览器端
        // CategoryExcelVo.class 表示excel对应的数据,sheet是表名
        EasyExcel.write(response.getOutputStream(), CategoryExcelVo.class)
.sheet("分类数据").doWrite(categoryExcelVoList);

    } catch (IOException e) {
        e.printStackTrace();
    }
}

 1.5 mapper层sql语句编写实现查询所有

2. 导入实现 

1.1 添加依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.0</version>
</dependency>

1.2 定义一个实体类来封装excel每一行的数据

1.3 Controller层需要MultipartFile表示上传的文件

@PostMapping("importData")
public Result importData(MultipartFile file) {
    categoryService.importData(file);
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}

 1.4 需要创建监听器

public class ExcelListener<T> extends AnalysisEventListener<T> {

    
    //存储数据库100条,然后清理list ,方便内存回收,cachedDataList是缓存的数据   
    private static final int BATCH_COUNT = 100;          
    private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

	//此类不能被spring管理,只能通过构造器!!!
    private CategoryMapper categoryMapper;
    public ExcelListener(CategoryMapper categoryMapper) {
        this.categoryMapper = categoryMapper;
    }

	// 每解析一行数据就会调用一次该方法
    @Override
    public void invoke(T o, AnalysisContext analysisContext) {  
        cachedDataList.add(o);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // excel解析完毕以后需要执行的代码
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
    }

    private void saveData() {
        //这里要强转下,强转成与excel对应的数据
        categoryMapper.batchInsert((List<CategoryExcelVo>)cachedDataList);
    }
}

1.5 Service层调用read

public void importData(MultipartFile file) {
    try {
        //创建监听器对象,传递mapper对象
        ExcelListener<CategoryExcelVo> excelListener = new ExcelListener<>(categoryMapper);
        //调用read方法读取excel数据
        EasyExcel.read(file.getInputStream(),
                    CategoryExcelVo.class,
                    excelListener).sheet().doRead();
    } catch (IOException e) {
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }
}

1.6 mapper层的sql语句编写实现插入数据库数据

21. mybatis与mybatis-plus的分页查询

1. mybatis分页查询

1.1 配置文件

1.1.1 yml文件
# mybatis的配置
mybatis:
  config-location: classpath:/mybatis-config.xml # 其他配置
  mapper-locations: classpath:/mapper/*.xml # 映射文件所在地
1.1.2 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>
    <settings>
        <!-- 设置驼峰标识 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 打印SQL语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <plugins>
        <!-- 分页插件 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
</configuration>

1.2 具体实现

1.2.1 controller层
    @GetMapping("/{page}/{limit}")
    @Operation(summary = "查询所有品牌接口")
    //page与limit是看前端传递来的名字
    public Result<PageInfo<Brand>> findAll(@PathVariable(value = "page") Integer page,
                                             @PathVariable(value = "limit")Integer limit){
        return Result.build(brandService.findAll(page, limit), ResultCodeEnum.SUCCESS);
    }
1.2.2 service层
    public PageInfo<Brand> findAll(Integer page, Integer limit) {
        PageHelper.startPage(page , limit);
        List<Brand> all = brandMapper.findAll(page, limit);
        PageInfo pageInfo = new PageInfo(all) ;
        return pageInfo;
    }

2. mybatis-plus分页查询

 22. 资源类的创建

有些配置文件需要用到一些常量数据,可以定义 properties 包存资源类

1. 编写yml配置文件,写下常量

test:
  testtwo:
    testthree:  
      - test
      - test
      - test

2. 创建properties包,新建资源类,加入以下注解

 注意与配置文件对应关系

@Data
@ConfigurationProperties(prefix = "test.testtwo")   
public class UserAuthProperties {
    private List<String> testthree;
}

3. 启动类扫描我们定义的资源类

//扫描我们定义的资源类
@EnableConfigurationProperties(value = {UserAuthProperties.class})
public class ManagerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ManagerApplication.class, args);
    }
}

23. 微服务之OpenFeign的使用

24. 微服务之解决OpenFeign的相互调用不携带请求头

25. 支付宝之实现支付功能

2.  AOP实现Log日志打印

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值