Java开发
基于SpringSecurity的用户权限控制

1.说明本文主要介绍基于SpringSecurity的用户权限控制的简单实现。1.1环境版本SpringBoot:2.0.7SpringSecurity:5.0.10JDK:1.82.项目配置2.1引入maven包<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="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.0https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.7.RELEASE</version><relativePath/><!--lookupparentfromrepository--></parent><groupId>cn.coralcloud</groupId><artifactId>security</artifactId><version>0.0.1-SNAPSHOT</version><name>security</name><description>DemoSecurityprojectforSpringBoot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.9.9</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.60</version></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>22.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>2.2application.ymlserver.port:9091spring.application.name:spring-webspring.http.encoding.charset:utf8spring:session:store-type:redisredis:host:localhostport:6379password:123456datasource:driver-class-name:com.mysql.jdbc.Driverurl:jdbc:mysql://127.0.0.1:3306/security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername:rootpassword:123456servlet:multipart:max-file-size:1024MBmax-request-size:1024MBmybatis:mapper-locations:classpath*:mapper/*Mapper.xmltype-aliases-package:cn.coralcloud.security.model2.3数据库初始化脚本/*NavicatMySQLDataTransferSourceServer:localhostSourceServerVersion:50644SourceHost:localhost:3306SourceDatabase:securityTargetServerType:MYSQLTargetServerVersion:50644FileEncoding:65001Date:2019-12-0216:28:44*/SETFOREIGN_KEY_CHECKS=0;--------------------------------Tablestructureforrole------------------------------DROPTABLEIFEXISTS`role`;CREATETABLE`role`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`name`varchar(50)NOTNULL,`nickname`varchar(50)NOTNULL,`system`bit(1)NOTNULLDEFAULTb'0',`description`varchar(500)DEFAULTNULL,`permission`mediumtext,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8mb4;--------------------------------Tablestructureforuser------------------------------DROPTABLEIFEXISTS`user`;CREATETABLE`user`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`username`varchar(50)NOTNULL,`password`varchar(300)NOTNULL,`role`varchar(500)DEFAULTNULL,PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8mb4;3.系统初始化文件3.1users.json[{"username":"admin","password":"ea48576f30be1669971699c09ad05c94","role":"ROLE_ADMINISTRATOR"}]3.2roles.json[{"name":"ROLE_ADMINISTRATOR","nickname":"管理员","description":"系统超级管理员,不允许用户更改","system":true,"permissions":[{"resourceId":"user","resourceName":"用户管理","privileges":{"list":"查看用户列表","add":"新增用户","update":"修改用户信息","delete":"删除用户"}},{"resourceId":"permission","resourceName":"权限","privileges":{"read":"查看权限","write":"新增权限","update":"更新权限","delete":"删除权限"}}]}]4.数据持久化DAO层4.1UserDao.javapackagecn.coralcloud.security.dao;importcn.coralcloud.security.model.User;importorg.apache.ibatis.annotations.Mapper;importorg.springframework.stereotype.Component;importjava.util.List;/***@authorgeff*@nameUserDao*@description*@date2019-11-2910:31*/@Component@MapperpublicinterfaceUserDao{/***根据用户名查找*@date2019/11/2915:24*@authorgeff*@paramusernameusername*@returncn.coralcloud.security.model.User*/UserfindByUsername(Stringusername);/***创建用户*@date2019/11/2915:24*@authorgeff*@paramuseruser*/voidsave(Useruser);/***获取用户列表*@date2019/11/2915:24*@authorgeff*@param*@returnjava.util.List<cn.coralcloud.security.model.User>*/List<User>list();}4.2RoleDao.javapackagecn.coralcloud.security.dao;importcn.coralcloud.security.model.Role;importorg.apache.ibatis.annotations.Mapper;importorg.springframework.stereotype.Component;/***@authorgeff*@nameRoleDao*@description*@date2019-11-2910:31*/@Component@MapperpublicinterfaceRoleDao{/***根据名称查找*@date2019/11/2915:23*@authorgeff*@paramnamename*@returncn.coralcloud.security.model.Role*/RolefindByName(Stringname);/***保存数据*@date2019/11/2915:23*@authorgeff*@paramrolerole*/voidsave(Rolerole);}4.3UserMapper.xml<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="cn.coralcloud.security.dao.UserDao"><resultMapid="userMapper"type="cn.coralcloud.security.model.User"><idproperty="id"column="id"/></resultMap><selectid="findByUsername"parameterType="String"resultMap="userMapper">select*from`user`where`username`=#{username}</select><selectid="list"resultType="cn.coralcloud.security.model.User">select*from`user`</select><insertid="save"parameterType="cn.coralcloud.security.model.User"useGeneratedKeys="true"keyProperty="id">insertinto`user`(username,password,role)values(#{username},#{password},#{role})</insert></mapper>4.4RoleMapper.xml<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTDMapper3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="cn.coralcloud.security.dao.RoleDao"><resultMapid="roleMapper"type="cn.coralcloud.security.model.Role"><idproperty="id"column="id"/></resultMap><selectid="findByName"parameterType="String"resultMap="roleMapper">select*fromrolewhere`name`=#{name}</select><insertid="save"parameterType="cn.coralcloud.security.model.Role"useGeneratedKeys="true"keyProperty="id">insertintorole(name,nickname,description,system,permission)values(#{name},#{nickname},#{description},#{system},#{permission})</insert></mapper>5.处理用户权限认证逻辑5.1SpringSecurity配置要在项目中使用@PreAuthorize等注解实现方法级别权限控制,则需要在项目启动类上添加注解@EnableGlobalMethodSecurity(prePostEnabled=true),本文项目启动类:packagecn.coralcloud.security;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.builder.SpringApplicationBuilder;importorg.springframework.boot.web.servlet.support.SpringBootServletInitializer;importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;/***@authorgeff*/@SpringBootApplication@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassSecurityApplicationextendsSpringBootServletInitializer{@OverrideprotectedSpringApplicationBuilderconfigure(SpringApplicationBuilderapplicationBuilder){returnapplicationBuilder.sources(SecurityApplication.class);}publicstaticvoidmain(String[]args){SpringApplication.run(SecurityApplication.class,args);}}5.2用户对象SpringSecurity自带的有UserDetails接口主要保存用户对象数据,所以我们的用户对象需要实现UserDetails接口packagecn.coralcloud.security.model;importcom.fasterxml.jackson.annotation.JsonIgnore;importlombok.Data;importlombok.NoArgsConstructor;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importjava.io.Serializable;importjava.util.*;/***@authorgeff*/@Data@NoArgsConstructorpublicclassUserimplementsUserDetails,Serializable{privateLongid;/***用户登录名*/privateStringusername;/***用户登录密码,用户的密码不应该暴露给客户端*/@JsonIgnoreprivateStringpassword;/***用户在系统中的角色列表,将根据角色对用户操作权限进行限制*/privateStringrole;privateList<Role>roles;@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){Collection<GrantedAuthority>authorities=newArrayList<>();if(roles!=null){for(Rolerole:roles){if(role==null){continue;}for(Permissionpermission:role.getPermissions()){for(Stringprivilege:permission.getPrivileges().keySet()){authorities.add(newSimpleGrantedAuthority(String.format("%s-%s",permission.getResourceId(),privilege)));}}}}returnauthorities;}@OverridepublicbooleanisAccountNonExpired(){returntrue;}@OverridepublicbooleanisAccountNonLocked(){returntrue;}@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}@OverridepublicbooleanisEnabled(){returntrue;}}在用户对象的publicCollection<?extendsGrantedAuthority>getAuthorities()方法中,需要根据用户当前角色生成当前用户权限列表,本文权限机制使用resourceId-privilege方式5.3角色对象Role.javapackagecn.coralcloud.security.model;importcom.alibaba.fastjson.JSON;importlombok.Data;importorg.springframework.util.StringUtils;importjava.io.Serializable;importjava.util.List;/***@authorgeff*@nameRole*@description*@date2019-11-2910:08*/@DatapublicclassRoleimplementsSerializable{privateLongid;/***角色名,用于权限校验*/privateStringname;/***角色中文名,用于显示*/privateStringnickname;/***角色描述信息*/privateStringdescription;/***是否为内置*/privateBooleansystem;/***角色可进行的操作列表*/privateList<Permission>permissions;privateStringpermission;/***SpringSecurity4.0以上版本角色都默认以'ROLE_'开头*@paramnamename*/publicvoidsetName(Stringname){if(!name.contains("ROLE_")){this.name="ROLE_"+name;}else{this.name=name;}}publicList<Permission>getPermissions(){if(permissions==null){if(!StringUtils.isEmpty(permission)){this.permissions=JSON.parseArray(permission,Permission.class);}}returnpermissions;}publicStringgetPermission(){if(StringUtils.isEmpty(permission)){this.permission=JSON.toJSONString(permissions);}returnpermission;}}SpringSecurity4.0以上所有的角色名称默认都要以ROLE_开头,所有本文在获取角色名称是会自动添加前缀。为了简单方便,本文角色对象的permission字段保存着该角色的所有权限列表的JSON字符串5.4权限对象Permission.javapackagecn.coralcloud.security.model;importlombok.Data;importjava.io.Serializable;importjava.util.Map;/***@authorgeff*@namePermission*@description*@date2019-11-2910:07*/@DatapublicclassPermissionimplementsSerializable{privateStringresourceId;privateStringresourceName;privateMap<String,String>privileges;privatebooleanabandon=false;}5.5统一数据返回对象因为是前后端分离项目,所以本文封装了统一数据返回对象Response类packagecn.coralcloud.security.model;importjava.io.Serializable;/***响应*@authorgeff*/publicclassResponse<T>implementsSerializable{privatefinalstaticintSUCCESS=0;privatefinalstaticintERROR=-1;privateintcode;privateTdata;privateStringmessage;publicintgetCode(){returncode;}publicvoidsetCode(intcode){this.code=code;}publicStringgetMessage(){returnmessage;}publicvoidsetMessage(Stringmessage){this.message=message;}publicTgetData(){returndata;}publicvoidsetData(Tdata){this.data=data;}publicstatic<T>Response<T>ok(){Response<T>response=newResponse<>();response.setCode(SUCCESS);returnresponse;}publicstatic<T>Response<T>ok(Tdata){Response<T>response=newResponse<>();response.setCode(SUCCESS);response.setData(data);returnresponse;}publicstatic<T>Response<T>fail(Stringmessage){Response<T>response=newResponse<>();response.setCode(ERROR);response.setMessage(message);returnresponse;}publicstatic<T>Response<T>fail(intcode,Stringmessage){Response<T>response=newResponse<>();response.setCode(code);response.setMessage(message);returnresponse;}}5.6UserDetailsService接口获取用户信息SpringSecurity同时在用户登录认证时会通过调用UserDetailsService的loadUserByUsername来获取当前登录的用户信息,当用户认证通过后会将用户对象保存到自定义的Token对象中。所以本文需要实现UserDetailsService接口,完成loadUserByUsername方法:packagecn.coralcloud.security.service;importcn.coralcloud.security.dao.RoleDao;importcn.coralcloud.security.model.Role;importcn.coralcloud.security.model.User;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importorg.springframework.util.StringUtils;importjava.util.ArrayList;importjava.util.List;/***@authorgeff*/@Service("myUserDetailsService")publicclassUserDetailsServiceImplimplementsUserDetailsService{@AutowiredprivateUserServiceuserService;@AutowiredprivateRoleDaoroleDao;@OverridepublicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{Useruser=userService.findByUsername(username);if(user==null){thrownewUsernameNotFoundException(String.format("Nouserfoundwithusername:%s",username));}if(!StringUtils.isEmpty(user.getRole())){String[]roles=user.getRole().split(",");List<Role>roleList=newArrayList<>();for(StringroleName:roles){Rolerole=roleDao.findByName(roleName);roleList.add(role);}user.setRoles(roleList);}returnuser;}}5.7自定义方法权限处理器自定义方法权限处理器需要实现PermissionEvaluator接口,完成publicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission)方法。packagecn.coralcloud.security.config;importcn.coralcloud.security.model.User;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.access.PermissionEvaluator;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.GrantedAuthority;importjava.io.Serializable;/***@authorgeff*/@ConfigurationpublicclassMyPermissionEvaluatorimplementsPermissionEvaluator{@OverridepublicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission){booleanaccessable=false;if(authentication.getPrincipal()instanceofUser){Stringprivilege=targetDomainObject+"-"+permission;for(GrantedAuthorityauthority:authentication.getAuthorities()){if(privilege.equalsIgnoreCase(authority.getAuthority())){accessable=true;break;}}returnaccessable;}returnfalse;}@OverridepublicbooleanhasPermission(Authenticationauthentication,SerializabletargetId,StringtargetType,Objectpermission){//TODOAuto-generatedmethodstubreturnfalse;}}本文通过根据resourceId-privilege方式验证权限5.8系统初始化配置类本文系统初始化配置类主要实现在系统启动时根据配置的users.json和roles.json自动生成初始化用户和角色信息。packagecn.coralcloud.security.component;importcn.coralcloud.security.dao.RoleDao;importcn.coralcloud.security.dao.UserDao;importcn.coralcloud.security.model.Role;importcn.coralcloud.security.model.User;importcom.google.gson.GsonBuilder;importcom.google.gson.reflect.TypeToken;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.beans.factory.annotation.Value;importjavax.annotation.PostConstruct;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.lang.reflect.Type;importjava.nio.charset.StandardCharsets;importjava.util.ArrayList;/***系统初始化配置类,主要用于加载内置数据到目标数据库上*@authorgeff*/@ComponentpublicclassSystemInitializer{@Value("${initialzation.file.users:users.json}")privateStringuserFileName;@Value("${initialzation.file.roles:roles.json}")privateStringroleFileName;@AutowiredprivateUserDaouserDao;@AutowiredprivateRoleDaoroleDao;@AutowiredprivateGsonBuildergsonBuilder;@PostConstructpublicbooleaninitialize(){try{InputStreamuserInputStream=getClass().getClassLoader().getResourceAsStream(userFileName);if(userInputStream==null){thrownewException("initialzationuserfilenotfound:"+userFileName);}InputStreamroleInputStream=getClass().getClassLoader().getResourceAsStream(roleFileName);if(roleInputStream==null){thrownewException("initialzationrolefilenotfound:"+roleFileName);}//导入初始的系统超级管理员角色TyperoleTokenType=newTypeToken<ArrayList<Role>>(){}.getType();ArrayList<Role>roles=gsonBuilder.create().fromJson(newInputStreamReader(roleInputStream,StandardCharsets.UTF_8),roleTokenType);for(Rolerole:roles){if(roleDao.findByName(role.getName())==null){roleDao.save(role);}}//导入初始的系统管理员用户TypeteacherTokenType=newTypeToken<ArrayList<User>>(){}.getType();ArrayList<User>users=gsonBuilder.create().fromJson(newInputStreamReader(userInputStream,StandardCharsets.UTF_8),teacherTokenType);for(Useruser:users){if(userDao.findByUsername(user.getUsername())==null){userDao.save(user);}}}catch(Exceptione){e.printStackTrace();}returntrue;}}6.用户认证相关自定义实现6.1.自定义用户认证过滤器​用户认证过滤器拦截用户发送的认证请求,然后从请求中获取用户账号和密码等认证信息并封装成一个未认证的AthenticationToken对象,然后调用AuthenticationManager对AthenticationToken进行认证。​自定义用户认证过滤器需要继承AbstractAuthenticationProcessingFilter,然后重写attemptAuthentication方法,在方法内部根据请求参数封装成未认证的AthenticationToken对象packagecn.coralcloud.security.component;importorg.springframework.security.authentication.AuthenticationServiceException;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;importorg.springframework.security.web.util.matcher.AntPathRequestMatcher;importorg.springframework.util.StringUtils;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/***自定义的用户名密码认证过滤器*@authorgeff*/publicclassAuthFilterextendsAbstractAuthenticationProcessingFilter{privatestaticStringhttpMethod="POST";publicAuthFilter(){/**设置该过滤器对POST请求/api/user/login进行拦截*/super(newAntPathRequestMatcher("/api/user/login",httpMethod));}@OverridepublicAuthenticationattemptAuthentication(HttpServletRequestrequest,HttpServletResponseresponse)throwsAuthenticationException{if(!request.getMethod().equals(httpMethod)){thrownewAuthenticationServiceException("Authenticationmethodnotsupported:"+request.getMethod());}else{/**从http请求中获取用户输入的用户名和密码信息*这里接收的是form形式的参数,如果要接收json形式的参数,修改这里即可*/Stringusername=this.obtainUsername(request);Stringpassword=this.obtainPassword(request);if(StringUtils.isEmpty(username)&&StringUtils.isEmpty(password)){thrownewUsernameNotFoundException("用户名或密码错误");}/**使用用户输入的用户名和密码信息创建一个未认证的用户认证Token*/AuthTokenauthRequest=newAuthToken(username,password);/**设置一些详情信息*/this.setDetails(request,authRequest);/**通过AuthenticationManager调用相应的AuthenticationProvider进行用户认证*/returnthis.getAuthenticationManager().authenticate(authRequest);}}privateStringobtainUsername(HttpServletRequestrequest){StringusernameParameter="username";returnrequest.getParameter(usernameParameter);}privateStringobtainPassword(HttpServletRequestrequest){StringpasswordParameter="password";returnrequest.getParameter(passwordParameter);}privatevoidsetDetails(HttpServletRequestrequest,AuthTokenauthRequest){authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}}自定义用户认证拦截器要在构造方法中指定拦截的认证请求(本文中是POST类型的/api/user/login请求,可根据需求设置),并在attemptAuthentication()方法中实现获取用户认证信息、封装AuthenticationToken对象、调用AuthenticationManager对AuthenticationToken进行认证等逻辑。6.2自定义用户认证处理器用户认证处理器主要是对用户提交的认证信息进行认证,SpringSecurity默认实现的认证处理器的认证处理逻辑并不一定符合所有的业务需求(例如,默认的认证处理无法处理验证码),因此,可以自定义用户认证处理器。自定义的用户认证处理器,需要实现AuthenticationProvider接口,主要是实现publicAuthenticationauthenticate(Authenticationauthentication)方法和publicbooleansupports(Class<?>authentication)方法,前者主要是实现具体的认证逻辑,后者主要是指定认证处理器能对哪种AuthenticationToken对象进行认证。packagecn.coralcloud.security.component;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.authentication.AuthenticationProvider;importorg.springframework.security.authentication.BadCredentialsException;importorg.springframework.security.authentication.dao.DaoAuthenticationProvider;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;importorg.springframework.stereotype.Component;/***@authorgeff*@nameAuthProvider*@description*登录认证的Provider,自定义实现了{@linkAuthenticationProvider}<br>*Provider默认实现是{@linkDaoAuthenticationProvider}<br>*{@linkUsernamePasswordAuthenticationFilter}调用=>{@linkAuthenticationManager}=>{@linkAuthenticationProvider}验证<br>**@date2019-11-2915:52*/@Slf4j@ComponentpublicclassAuthProviderimplementsAuthenticationProvider{privatefinalUserDetailsServiceuserDetailService;privatefinalPasswordEncoderpasswordEncoder;@AutowiredpublicAuthProvider(@Qualifier("myUserDetailsService")UserDetailsServiceuserDetailService,PasswordEncoderpasswordEncoder){this.userDetailService=userDetailService;this.passwordEncoder=passwordEncoder;}@OverridepublicAuthenticationauthenticate(Authenticationauthentication)throwsAuthenticationException{/**将未认证的Authentication转换成自定义的用户认证Token*/AuthTokenauthenticationToken=(AuthToken)authentication;/**根据用户Token中的用户名查找用户信息,如果有该用户信息,则验证用户密码是否正确*/UserDetailsuser=userDetailService.loadUserByUsername((String)(authenticationToken.getPrincipal()));if(user==null){thrownewBadCredentialsException("用户名或密码不正确");}elseif(!this.passwordEncoder.matches((CharSequence)authenticationToken.getCredentials(),user.getPassword())){thrownewBadCredentialsException("用户名或密码不正确");}/**认证成功则创建一个已认证的用户认证Token*/AuthTokenauthenticationResult=newAuthToken(user,user.getPassword(),user.getAuthorities());/**设置一些详情信息*/authenticationResult.setDetails(authenticationToken.getDetails());returnauthenticationResult;}/***是否支持处理当前Authentication对象类似*/@Overridepublicbooleansupports(Class<?>authentication){returntrue;}}6.3自定义用户认证对象用户认证对象是在用户认证拦截器中创建的,在用户认证处理器中使用的。用户认证对象(AuthenticationToken)中封装的是用户认证信息,例如UsernamePasswordAuthenticationToken中封装的是用户名和密码。实际业务中,可能需要根据不同的用户信息进行认证(例如,手机号和验证码),此时就需要自定义用户认证对象。自定义的用户认证对象,需要继承AbstractAuthenticationToken类,并设定根据认证时使用的是哪些信息。packagecn.coralcloud.security.component;importorg.springframework.security.authentication.AbstractAuthenticationToken;importorg.springframework.security.core.GrantedAuthority;importjava.util.Collection;/***自定义的用户名密码认证对象*@authorgeff*/publicclassAuthTokenextendsAbstractAuthenticationToken{/***用户名*/privatefinalObjectprincipal;/***密码*/privateObjectcredentials;/***创建未认证的用户名密码认证对象*/publicAuthToken(Objectprincipal,Objectcredentials){super(null);this.principal=principal;this.credentials=credentials;this.setAuthenticated(false);}/***创建已认证的用户密码认证对象*/publicAuthToken(Objectprincipal,Objectcredentials,Collection<?extendsGrantedAuthority>authorities){super(authorities);this.principal=principal;this.credentials=credentials;super.setAuthenticated(true);}@OverridepublicObjectgetCredentials(){returnthis.credentials;}@OverridepublicObjectgetPrincipal(){returnthis.principal;}@OverridepublicvoidsetAuthenticated(booleanisAuthenticated)throwsIllegalArgumentException{if(isAuthenticated){thrownewIllegalArgumentException("Cannotsetthistokentotrusted-useconstructorwhichtakesaGrantedAuthoritylistinstead");}else{super.setAuthenticated(false);}}@OverridepublicvoideraseCredentials(){super.eraseCredentials();this.credentials=null;}}6.4自定义用户认证成功处理器用户认证成功处理器在用户认证成功之后调用,主要是执行一些额外的操作(例如,操作Cookie、页面跳转等)。自定义的用户认证成功处理器可以通过实现AuthenticationSuccessHandler接口,或者通过继承AbstractAuthenticationTargetUrlRequestHandler类及其子类来实现。本文自定义的用户认证成功处理器是通过继承AbstractAuthenticationTargetUrlRequestHandler的子类SavedRequestAwareAuthenticationSuccessHandler来实现的。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.User;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.Authentication;importorg.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjava.io.IOException;/***自定义的用户认证成功处理器*@authorgeff*/@Component@Slf4jpublicclassAuthSuccessHandlerextendsSavedRequestAwareAuthenticationSuccessHandler{@AutowiredprivateObjectMapperobjectMapper;publicAuthSuccessHandler(){}@OverridepublicvoidonAuthenticationSuccess(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException{//认证成功返回jsonUseruser=(User)authentication.getPrincipal();//写入session?HttpSessionsession=request.getSession();session.setAttribute("User",user);StringjsonStr=objectMapper.writeValueAsString(user);log.info("认证成功:{}",jsonStr);response.setContentType("application/json;charset=UTF-8");response.getWriter().write(jsonStr);}}6.5自定义用户认证失败处理器用户认证失败处理器是在用户认证失败之后调用,主要是执行一些额外的操作(例如操作Cookie、页面跳转、返回错误信息等)。自定义的用户认证失败处理器可以通过实现AuthenticationFailureHandler接口,或者通过继承AuthenticationFailureHandler接口的其它实现类来实现。本文自定义的用户认证失败处理器是通过继承AuthenticationFailureHandler接口的实现类SimpleUrlAuthenticationFailureHandler来实现的。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.Response;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***自定义的用户认证失败处理器*@authorgeff*/@Component@Slf4jpublicclassAuthFailureHandlerextendsSimpleUrlAuthenticationFailureHandler{@AutowiredprivateObjectMapperobjectMapper;@OverridepublicvoidonAuthenticationFailure(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionexception)throwsIOException{/**返回JSON*/log.error("认证失败:{}",exception.getMessage());Responseres=Response.fail(-1,exception.getMessage());response.setStatus(HttpStatus.OK.value());response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(res));response.getWriter().flush();}}6.6自定义访问拒绝处理器自定义访问拒绝处理器用来解决认证过的用户访问无权限资源时的异常。前后端分离的情况下可以通过自定义访问拒绝处理器实现JSON格式的数据返回,自定义访问拒绝处理器通过实现AccessDeniedHandler接口,然后实现publicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptione)方法,在方法内部处理返回数据。packagecn.coralcloud.security.component;importcn.coralcloud.security.model.Response;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.HttpStatus;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.web.access.AccessDeniedHandler;importorg.springframework.stereotype.Component;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@authorgeff*@nameAuthAccessDeniedHandler*@description*@date2019-11-2916:57*/@Slf4j@ComponentpublicclassAuthAccessDeniedHandlerimplementsAccessDeniedHandler{@AutowiredprivateObjectMapperobjectMapper;@Overridepublicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptione)throwsIOException{log.error("无权访问:{}",e.getMessage());Responseres=Response.fail(401,"无权访问");response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(res));response.getWriter().flush();}}6.7自定义加密类本文密码使用Md5(password,salt)的形式,所以需要自定义SpringSecurity加密类,然后再Config配置类注入自定义加密类需要实现PasswordEncoder,完成encode和matches方法packagecn.coralcloud.security.component;importcn.coralcloud.security.utils.SecretUtils;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.util.StringUtils;importjava.util.Objects;/***@authorgeff*@nameMd5SaltPasswordEncoder*@description*@date2019-12-0209:27*/publicclassMd5SaltPasswordEncoderimplementsPasswordEncoder{@OverridepublicStringencode(CharSequencecharSequence){Stringstring=charSequence.toString();String[]array=string.split(",");Stringsalt="";if(array.length>1){salt=array[1];}returnSecretUtils.md5(array[0],salt);}@Overridepublicbooleanmatches(CharSequencecharSequence,Strings){if(!StringUtils.isEmpty(s)){StringencodePassword=encode(charSequence);returnObjects.equals(encodePassword,s);}returnfalse;}}7.SpringSecurity相关配置类7.1自定义的用户名密码认证配置类packagecn.coralcloud.security.component;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.config.annotation.SecurityConfigurerAdapter;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.web.DefaultSecurityFilterChain;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;importorg.springframework.stereotype.Component;/***自定义的用户名密码认证配置类*@authorgeff*/@ComponentpublicclassAuthConfigextendsSecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity>{@AutowiredAuthProviderprovider;@AutowiredAuthSuccessHandlerauthSuccessHandler;@AutowiredAuthFailureHandlerauthFailureHandler;@Overridepublicvoidconfigure(HttpSecurityhttp){AuthFilterauthFilter=newAuthFilter();/**自定义用户认证处理逻辑时,需要指定AuthenticationManager,否则无法认证*/authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));/**指定自定义的认证成功和失败的处理器*/authFilter.setAuthenticationSuccessHandler(authSuccessHandler);authFilter.setAuthenticationFailureHandler(authFailureHandler);/**把自定义的用户名密码认证过滤器和处理器添加到UsernamePasswordAuthenticationFilter过滤器之前*/http.authenticationProvider(provider).addFilterBefore(authFilter,UsernamePasswordAuthenticationFilter.class);}}7.2SpringSecurity核心配置类packagecn.coralcloud.security.config;importcn.coralcloud.security.component.AuthAccessDeniedHandler;importcn.coralcloud.security.component.AuthConfig;importcn.coralcloud.security.component.Md5SaltPasswordEncoder;importcn.coralcloud.security.model.Response;importcn.coralcloud.security.service.UserDetailsServiceImpl;importcom.fasterxml.jackson.databind.ObjectMapper;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.crypto.password.PasswordEncoder;importjava.io.PrintWriter;/***@authorgeff*/@Configuration@EnableWebSecurity@Slf4jpublicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateAuthConfigauthConfig;@AutowiredprivateAuthAccessDeniedHandleraccessDeniedHandler;@Overrideprotectedvoidconfigure(HttpSecurityhttpSecurity)throwsException{httpSecurity.apply(authConfig).and().authorizeRequests().antMatchers("/static/**","/api/user/login").permitAll().anyRequest().authenticated().and().csrf().disable();httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint((request,response,e)->{response.setContentType("application/json;charset=utf-8");PrintWriterout=response.getWriter();Responseres=Response.fail(-14,"会话超时,请重新登录!");out.write(newObjectMapper().writeValueAsString(res));out.flush();out.close();});}@AutowiredpublicvoidconfigureGlobal(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewMd5SaltPasswordEncoder();}@Bean@OverridepublicUserDetailsServiceuserDetailsService(){returnnewUserDetailsServiceImpl();}}本文通过自定义AuthenticationEntryPoint来解决匿名用户访问无权限资源时的异常

Tags: JAVA
个人随笔
Centos7安装Redis

一、安装Redis1.下载Rediswgethttp://download.redis.io/releases/redis-4.0.6.tar.gz2.解压tar-zxvfredis-4.0.6.tar.gz3.yum安装gccyuminstall-ygcc4.编译安装rediscdredis-4.0.6makeMALLOC=libccdsrc&&makeinstall二、启动Redis1.在/etc目录下新建redis目录mkdir-p/etc/redis2.将/usr/local/redis-4.0.6/redis.conf文件复制一份到/etc/redis目录下,并命名为6379.confcp/usr/local/redis-4.0.6/redis.conf/etc/redis/6379.conf3.将redis的启动脚本复制一份放到/etc/init.d目录下cp/usr/local/redis-4.0.6/utils/redis_init_script/etc/init.d/redisd4.设置redis开机自启动cd/etc/init.dchkconfigredisdonserviceredisddoesnotsupportchkconfig看结果是redisd不支持chkconfig,编辑redisd文件,加入如下注释:#chkconfig:23459010#description:Redisisapersistentkey-valuedatabasechkconfigredisdon启动redis:serviceredisdstart停止redis:serviceredisdstop

大数据开发
Centos安装ApacheHadoop2.7.7

Centos安装ApacheHadoop2.7.7准备文件:hadoop-2.7.7.tar.gz一、安装JDK安装Hadoop之前需要保证JDK8成功安装java-version二、上传Hadoop压缩包上传成功后解压文件:tar-zxvfhadoop-2.7.7.tar.gz假设解压后的Hadoop目录为:/home/hadoop/hadoop-2.7.7注意配置HADOOP_HOME环境变量exportHADOOP_HOME=/home/hadoop/hadoop-2.7.7exportPATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin三、配置服务器免密登录$ssh-keygen-trsa-P''-f~/.ssh/id_rsa$cat~/.ssh/id_rsa.pub>>~/.ssh/authorized_keys$chmod0600~/.ssh/authorized_keys执行完成后:sshmaster命令则能够跳过输入密码阶段直接连接四、修改配置==配置文件所在位置:$HADOOP_HOME/etc/hadoop==core-size.xml<configuration><property><name>fs.defaultFS</name><value>hdfs://master:9000</value></property></configuration>hdfs-size.xml<configuration><property><name>dfs.replication</name><value>1</value></property></configuration>mapred-site.xml<configuration><property><name>mapreduce.framework.name</name><value>yarn</value></property><property><name>mapreduce.application.classpath</name><value>$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*</value></property></configuration>yarn-site.xml<configuration><property><name>yarn.nodemanager.aux-services</name><value>mapreduce_shuffle</value></property><property><name>yarn.nodemanager.env-whitelist</name><value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value></property></configuration>五、启动初始化HDFShdfsnamenode-format启动HDFSstart-dfs.sh启动YARNstart-yarn.sh启动成功后访问http://master:50070查看HDFS状态六、相关命令start-dfs.sh启动HDFS(namenode/datanode/SecondaryNamenode)stop-dfs.sh停止HDFSstart-yarn.sh启动Yarnstop-yarn.sh停止Yarnstart-all.sh启动HDFS和Yarnstop-all.sh停止HDFS和Yarn

Java开发
MyBatis之where关键字与<where>标签的区别

1.在使用mybatis的动态sql时,有时候遇到根据条件判断添加where后面的筛选条件的情况,会出现多余的AND或者OR:2.使用where关键字:2.1当第一个参数为空时,拼接后的sql为:select*fromtdwhereandphone=.......;2.2当所有的参数都为空时,拼接后的sql为:select*fromtdwhere.....,显然这样的sql不是完整的sql,执行时会报错.3.使用where标签时:3.1当第一个参数为空时,拼接后的sql为:select*fromtdwherephone=......(若语句的开头为AND或者OR时,where元素会将他们去除).3.2当所有的参数都为空时,拼接后的sql为:select*fromtd.(where元素只会在至少有一个子元素的条件返回SQL子句的情况下才去插入“WHERE”子句)。

Tags: JAVA
Java开发
文件上传之@RequestParam与@RequestPart

1.、@RequestParam与@RequestPart主要用来接收文件,两者都能用于后端接收文件2.@RequestPart这个注解用在multipart/form-data表单提交请求的方法上。支持的请求方法的方式MultipartFile,属于Spring的MultipartResolver类。这个请求是通过http协议传输的。3.@RequestParam也同样支持multipart/form-data请求。当请求方法的请求参数类型是String类型的时候。4.@RequestParam适用于name-valueString类型的请求域,@RequestPart适用于复杂的请求域(像JSON,XML)5.@RequestPart注解会生成临时文件,而@RequestParam则不会生成临时文件,效率上ReqeustParam会比RequestPart快一些

Tags: JAVA
个人随笔
Vue + SpringBoot实现WebSocket通信

Vue+SpringBoot实现WebSocket通信服务端在SpringBoot项目中添加ServerEndpointExporterBean的方法@BeanpublicServerEndpointExporterexporter(){returnnewServerEndpointExporter();}创建WebSocket客户端管理类:WebSocketComponent.javapackagecn.coralcloud.blog.web.component;importcom.alibaba.fastjson.JSON;importorg.springframework.stereotype.Component;importjavax.websocket.*;importjavax.websocket.server.ServerEndpoint;importjava.io.IOException;importjava.util.Objects;importjava.util.concurrent.CopyOnWriteArraySet;/**@authorgeff@nameWebSocketComponent@description@date2019-12-1814:22/@ServerEndpoint(value=&quot;/websocket&quot;)@ComponentpublicclassWebSocketComponent{/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。/privatestaticintonlineCount=0;/**concurrent包的线程安全Set,用来存放每个客户端对应的CumWebSocket对象。/privatestaticCopyOnWriteArraySetwebSocketSet=newCopyOnWriteArraySet&lt;&gt;();/**与某个客户端的连接会话,需要通过它来给客户端发送数据/privateSessionsession;/**连接建立成功调用的方法@paramsessionsession/@OnOpenpublicvoidonOpen(Sessionsession){this.session=session;//加入set中webSocketSet.add(this);//添加在线人数addOnlineCount();System.out.println(&quot;新连接接入。当前在线人数为:&quot;+getOnlineCount());}/**连接关闭调用的方法/@OnClosepublicvoidonClose(){//从set中删除webSocketSet.remove(this);//在线数减1subOnlineCount();System.out.println(&quot;有连接关闭。当前在线人数为:&quot;+getOnlineCount());}/**收到客户端消息后调用@parammessagemessage@paramsessionsession/@OnMessagepublicvoidonMessage(Stringmessage,Sessionsession){System.out.println(&quot;客户端发送的消息:&quot;+message);sendAll(JSON.toJSONString(messageDTO),session.getId());}/**群发@parammessagemessage/privatestaticvoidsendAll(Stringmessage,StringsessionId){webSocketSet.forEach(item-&gt;{if(!item.session.getId().equals(sessionId)){//群发try{item.sendMessage(message);}catch(IOExceptione){e.printStackTrace();}}});}/**发生错误时调用@paramsessionsession@paramerrorerror/@OnErrorpublicvoidonError(Sessionsession,Throwableerror){System.out.println(&quot;----websocket-------有异常啦&quot;);error.printStackTrace();}/**减少在线人数/privatevoidsubOnlineCount(){WebSocketComponent.onlineCount--;}/**添加在线人数/privatevoidaddOnlineCount(){WebSocketComponent.onlineCount++;}/**当前在线人数@returnint/publicstaticsynchronizedintgetOnlineCount(){returnonlineCount;}/**发送信息@parammessagemessagethrowsIOException/publicvoidsendMessage(Stringmessage)throwsIOException{//获取session远程基本连接发送文本消息this.session.getBasicRemote().sendText(message);//this.session.getAsyncRemote().sendText(message);}@Overridepublicbooleanequals(Objecto){if(this==o){returntrue;}if(o==null||getClass()!=o.getClass()){returnfalse;}WebSocketComponentthat=(WebSocketComponent)o;returnObjects.equals(session,that.session);}@OverridepublicinthashCode(){returnObjects.hash(session);}}@ServerEndpoint注解标识当前WebSocket服务端endpoint地址,本文实际前端访问的ws地址为:ws://localhost:8080/websocket。至此,服务端工作完成。页面VUE端&lt;template&gt;&lt;el-cardv-loading=&quot;loading&quot;element-loading-spinner=&quot;el-icon-loading&quot;:body-style=&quot;{padding:&#39;5px&#39;,backgroundColor:&#39;#eee&#39;}&quot;class=&quot;socket-box&quot;shadow=&quot;hover&quot;&gt;&lt;divclass=&quot;socket-box__content&quot;:style=&quot;{height:(boxHeight-125)+&#39;px&#39;}&quot;id=&quot;socket-content&quot;&gt;&lt;divv-if=&quot;hasMore&quot;@click=&quot;loadMore&quot;class=&quot;load-more&quot;&gt;&lt;span&gt;加载更多&lt;/span&gt;&lt;/div&gt;&lt;divv-elsestyle=&quot;width:100%;text-align:center;font-size:12px&quot;&gt;没有更多了&lt;/div&gt;&lt;divclass=&quot;item&quot;v-for=&quot;minmessages&quot;:class=&quot;checkMe(m)?&#39;sender&#39;:&#39;&#39;&quot;&gt;&lt;divclass=&quot;slide&quot;&gt;&lt;divclass=&quot;avatar&quot;:style=&quot;{background:m.background}&quot;&gt;{{m.name.substring(0,1)}}&lt;/div&gt;&lt;divclass=&quot;meta&quot;&gt;&lt;divclass=&quot;name&quot;&gt;{{m.name}}&lt;/div&gt;&lt;divclass=&quot;date&quot;&gt;{{m.createTime|datetime}}&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;{{m.content}}&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;divclass=&quot;socket-box__footer&quot;&gt;&lt;el-form@submit.native.prevent&gt;&lt;el-form-item&gt;&lt;el-inputtype=&quot;textarea&quot;resize=&quot;none&quot;:rows=&quot;3&quot;:disabled=&quot;!connect&quot;:placeholder=&quot;connect?&#39;输入内容...&#39;:&#39;当前连接断开,请刷新重试!&#39;&quot;:clearable=&quot;true&quot;v-model=&quot;message&quot;@keydown.native.enter=&quot;submitMsgForm&quot;&gt;&lt;/el-input&gt;&lt;/el-form-item&gt;&lt;el-form-item&gt;&lt;el-button@click=&quot;sendMsg(message)&quot;:disabled=&quot;!connect&quot;style=&quot;width:100%&quot;type=&quot;primary&quot;size=&quot;small&quot;&gt;发送(Enter)&lt;/el-button&gt;&lt;/el-form-item&gt;&lt;/el-form&gt;&lt;/div&gt;&lt;/el-card&gt;&lt;/template&gt;&lt;script&gt;import{GET}from&quot;@/api&quot;;exportdefault{name:&quot;Chatroom&quot;,data(){return{messages:[],message:&#39;&#39;,//boxHeight:document.documentElement.clientHeight-85,hasMore:true,pager:{pageNo:1,pageSize:10,total:0},loading:false,connect:false}},props:{boxHeight:{type:Number,required:true}},methods:{submitMsgForm(event){if(event.shiftKey){return;}event.preventDefault();this.sendMsg(this.message)},checkMe(message){letuser=localStorage.getItem(&quot;socketUser&quot;);if(user){user=JSON.parse(user);returnuser.uid===message.uid}else{returnfalse;}},initWebSocket:function(){this.websock=newWebSocket(`ws://localhost:8080/websocket`);this.websock.onopen=this.websocketonopen;this.websock.onerror=this.websocketonerror;this.websock.onmessage=this.websocketonmessage;this.websock.onclose=this.websocketclose;constthat=this;that.loading=true;GET({url:&#39;/api/personal/web/message/socketData?pageNo=1&#39;,callback:res=&gt;{if(res.code===200){that.messages=res.data.messages;that.hasMore=res.data.messages.length===that.pager.pageSize;that.$nextTick(function(){document.getElementById(&quot;socket-content&quot;).scroll({top:document.getElementById(&quot;socket-content&quot;).scrollHeight,left:0,behavior:&#39;smooth&#39;})})}that.loading=false}})},sendMsg(data){if(/^\s*$/.test(data)){this.message=&#39;&#39;;return;}//发送时传入JSON(UID,昵称,内容)constlocal=localStorage.getItem(&quot;socketUser&quot;);if(local){constl=JSON.parse(local);this.send(l,data)}else{//弹框this.$prompt(&#39;首次发表,请输入昵称&#39;,&#39;提示&#39;,{confirmButtonText:&#39;确定&#39;,cancelButtonText:&#39;取消&#39;,}).then(({value})=&gt;{//随机生成UIDconstuid=this.randomVideoUuid(32,16);constform={uid:uid,name:value,background:`rgb(${Math.random()*255},${Math.random()*255},${Math.random()*255})`};localStorage.setItem(&quot;socketUser&quot;,JSON.stringify(form));this.send(form,data)}).catch(()=&gt;{this.$message({type:&#39;info&#39;,message:&#39;取消输入&#39;});});}},send(obj,data){obj.content=data;obj.createTime=newDate().getTime();this.websock.send(JSON.stringify(obj));this.message=&#39;&#39;;this.messages.push(obj);this.$nextTick(function(){document.getElementById(&quot;socket-content&quot;).scroll({top:document.getElementById(&quot;socket-content&quot;).scrollHeight,left:0,behavior:&#39;smooth&#39;})})},loadMore(){this.loading=true;constthat=this;if(this.hasMore){this.pager.pageNo+=1;GET({url:&#39;/api/personal/web/message/socketData?pageNo=&#39;+this.pager.pageNo,callback:res=&gt;{if(res.code===200){that.messages=[...res.data.messages,...that.messages];that.hasMore=res.data.messages.length&gt;=that.pager.pageSize;}that.loading=false;}})}},websocketonopen:function(e){console.log(&quot;WebSocket连接成功&quot;,e);this.connect=true;},websocketonerror:function(e){console.log(&quot;WebSocket连接发生错误&quot;);this.connect=false;},websocketonmessage:function(e){constda=JSON.parse(e.data);this.messages.push(da);this.$nextTick(function(){document.getElementById(&quot;socket-content&quot;).scroll({top:document.getElementById(&quot;socket-content&quot;).scrollHeight,left:0,behavior:&#39;smooth&#39;})})},websocketclose:function(e){console.log(&quot;connectionclosed(&quot;+e.code+&quot;)&quot;);},randomVideoUuid(len,radix){letchars=&#39;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&#39;.split(&#39;&#39;);letuuid=[];radix=radix||chars.length;if(len){for(leti=0;i&lt;len;i++)uuid[i]=chars[0|Math.random()*radix];}else{letr;uuid[8]=uuid[13]=uuid[18]=uuid[23]=&#39;-&#39;;uuid[14]=&#39;4&#39;;for(leti=0;i&lt;36;i++){if(!uuid[i]){r=0|Math.random()*16;uuid[i]=chars[(i===19)?(r&amp;0x3)|0x8:r];}}}returnuuid.join(&#39;&#39;);},},mounted(){this.initWebSocket();}}&lt;/script&gt;本段代码为简单的Vue实现的网页聊天室代码,其中@/api为自己简单封装的JS函数,用户初次进入页面时会生成一个随机UID保存到localStorage中,在mounted周期中初始化websocket连接。本聊天室最终效果地址:https://web.coralcloud.cn/blog/message

Java开发
SpringBoot框架之@Controller和@RestController的区别?

1.@RestController注解相当于@ResponseBody+@Controller合在一起的作用。2.如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是Return里的内容。3.如果需要返回到指定页面,则需要用@Controller配合视图解析器InternalResourceViewResolver才行。4.如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。)

Tags: JAVA
大数据开发
CentOS7安装CDH5

CentOS7的ClouderaManager5.8安装系统要求http://www.cloudera.com/documentation/manager/5-1-x/Cloudera-Manager-Installation-Guide/cm5ig_cm_requirements.html内存(最低要求):主机4G节点2G相关包下载:ClouderaManager下载地址:http://archive.cloudera.com/cm5/cm/5/选择cloudera-manager-centos7-cm5.8.0_x86_64.tar.gzCDH5下载地址:http://archive.cloudera.com/cdh5/parcels/5/选择以下三个文件:CDH-5.8.0-1.cdh5.8.0.p0.42-el7.parcelCDH-5.8.0-1.cdh5.8.0.p0.42-el7.parcel.sha1(下载后重命名为CDH-5.8.0-1.cdh5.8.0.p0.42-el7.parcel.sha)manifest.jsonJDK下载地址:http://www.oracle.com/technetwork/java/javase/downloads选择最新的JDKrpm包cmagent与cmdaemons下载地址:http://archive-primary.cloudera.com/cm5/redhat/7/x86_64/cm/5.8/RPMS/x86_64/选择以下两个文件:cloudera-manager-agent-5.8.1-1.cm581.p0.7.el7.x86_64.rpmcloudera-manager-daemons-5.8.1-1.cm581.p0.7.el7.x86_64.rpmMysqlJDBC驱动下载地址:http://dev.mysql.com/downloads/connector/j/选择mysql-connector-java-5.1.45.tar.gz准备工作:系统环境搭建++以下操作均用root用户操作++1.网络配置(所有节点)vi/etc/sysconfig/network修改hostname:NETWORKING=yesHOSTNAME=master通过servicenetworkrestart重启网络服务生效。vi/etc/hosts修改ip与主机名的对应关系192.168.1.101master192.168.1.102slave1192.168.1.103slave2192.168.1.104slave3注意:这里需要将每台机器的ip及主机名对应关系都写进去,本机的也要写进去,否则启动Agent的时候会提示hostname解析错误。2.打通SSH,设置ssh无密码登陆(所有节点)在主节点上执行ssh-keygen-trsa一路回车,生成无密码的密钥对。将公钥添加到认证文件中:Cat~/.ssh/id_rsa.pub&gt;&gt;~/.ssh/authorized_keys并设置authorized_keys的访问权限:chmod600~/.ssh/authorized_keysscp文件到所有datenode节点:scp~/.ssh/authorized_keysroot@slave1:~/.ssh/scp~/.ssh/authorized_keysroot@slave2:~/.ssh/scp~/.ssh/authorized_keysroot@slave3:~/.ssh/在主节点上sshslave1,正常情况下,不需要密码就能直接登陆进去了。3.安装Oracle的Java(所有节点)CentOS,自带OpenJdk,不过运行CDH5需要使用Oracle的Jdk,需要Java7的支持。卸载自带的OpenJdk,使用rpm-qa|grepjava查询java相关的包,使用rpm-e--nodeps包名卸载之。去Oracle的官网下载jdk的rpm安装包,并使用rpm-ivh包名安装之。由于是rpm包并不需要我们来配置环境变量,我们只需要配置一个全局的JAVA_HOME变量即可,执行命令:echo&quot;JAVA_HOME=/usr/java/latest/&quot;&gt;&gt;/etc/environment4.安装配置MySql(主节点)通过yuminstallmysql-server安装mysql服务器。chkconfigmysqldon设置开机启动,并servicemysqldstart启动mysql服务,并根据提示设置root的初试密码:mysqladmin-urootpassword&#39;123456&#39;mysql-uroot-p123456进入mysql命令行,创建以下数据库:#hivecreatedatabasehiveDEFAULTCHARSETutf8COLLATEutf8_general_ci;#activitymonitorcreatedatabaseamonDEFAULTCHARSETutf8COLLATEutf8_general_ci;设置root授权访问以上所有的数据库:#授权root用户在主节点拥有所有数据库的访问权限grantallprivilegeson*.*to&#39;root&#39;@&#39;master&#39;identifiedby&#39;123456&#39;withgrantoption;flushprivileges;5.关闭防火墙和SELinux注意:需要在所有的节点上执行,因为涉及到的端口太多了,临时关闭防火墙是为了安装起来更方便,安装完毕后可以根据需要设置防火墙策略,保证集群安全。关闭防火墙:serviceiptablesstop(临时关闭)chkconfigiptablesoff(重启后生效)关闭SELINUX(实际安装过程中发现没有关闭也是可以的,不知道会不会有问题,还需进一步进行验证):setenforce0(临时生效)修改/etc/selinux/config下的SELINUX=disabled(重启后永久生效)CentOS7.0systemctlstopfirewalld.service#停止firewallsystemctldisablefirewalld.service#禁止firewall开机启动6.所有节点配置NTP服务集群中所有主机必须保持时间同步,如果时间相差较大会引起各种问题。具体思路如下:master节点作为ntp服务器与外界对时中心同步时间,随后对所有datanode节点提供时间同步服务。所有datanode节点以master节点为基础同步时间。所有节点安装相关组件:yuminstallntp。完成后,配置开机启动:chkconfigntpdon,检查是否设置成功:chkconfig--listntpd其中2-5为on状态就代表成功。centos7:systemctlenablentpdsystemctlis-enabledntpd1.主节点配置位置/etc/ntp.conf在配置之前,先使用ntpdate手动同步一下时间,免得本机与对时中心时间差距太大,使得ntpd不能正常同步。这里选用65.55.56.206作为对时中心,ntpdate-u65.55.56.206。ntp服务只有一个配置文件,配置好了就OK。这里只给出有用的配置,不需要的配置都用#注掉,这里就不在给出:driftfile/var/lib/ntp/driftrestrict127.0.0.1restrict-6::1restrictdefaultnomodifynotrapservercn.ntp.org.cnpreferincludefile/etc/ntp/crypto/pwkeys/etc/ntp/keys配置文件完成,保存退出,启动服务,执行如下命令:1servicentpdstart2ntpstat检查是否成功,用ntpstat命令查看同步状态,出现以下状态代表启动成功:synchronisedtoNTPserver()atstratum2timecorrecttowithin74mspollingserverevery128s如果出现异常请等待几分钟,一般等待5-10分钟才能同步。配置ntp客户端(所有datanode节点)driftfile/var/lib/ntp/driftrestrict127.0.0.1restrict-6::1restrictdefaultkodnomodifynotrapnopeernoqueryrestrict-6defaultkodnomodifynotrapnopeernoquery#这里是主节点的主机名或者ipservermasterincludefile/etc/ntp/crypto/pwkeys/etc/ntp/keysok保存退出,请求服务器前,请先使用ntpdate手动同步一下时间:ntpdate-umaster(主节点ntp服务器)这里可能出现同步失败的情况,请不要着急,一般是本地的ntp服务器还没有正常启动,一般需要等待5-10分钟才可以正常同步。启动服务:servicentpdstart因为是连接内网,这次启动等待的时间会比master节点快一些,但是也需要耐心等待一会儿正式开工安装CMagent和CMDaemons安装先用scp将cmagent与cmdaemons复制到所有节点用yum进行安装:yum--nogpgchecklocalinstallcloudera-manager-agent-*.x86_64.rpmyum--nogpgchecklocalinstallcloudera-manager-daemons-*.x86_64.rpm安装ClouderaManagerServer和Agent主节点解压安装clouderamanager的目录默认位置在/opt下,解压:tarxzvfcloudera-manager*.tar.gz将解压后的cm-5.8.0和cloudera目录放到/opt目录下。为ClouderaManager5建立数据库:首先需要去MySql的官网下载JDBC驱动,http://dev.mysql.com/downloads/connector/j/,解压后,找到mysql-connector-java-5.1.45-bin.jar,放到/opt/cm-5.8.0/share/cmf/lib/中。在主节点初始化CM5的数据库:/opt/cm-5.8.0/share/cmf/schema/scm_prepare_database.shmysqlcm-hlocalhost-uroot-pxxxx--scm-hostlocalhostscmscmscmAgent配置修改/opt/cm-5.8.0/etc/cloudera-scm-agent/config.ini中的server_host为主节点的主机名。同步Agent到其他节点scp-r/opt/cm-5.8.0root@slave1:/opt/在所有节点创建cloudera-scm用户useradd--system--home=/opt/cm-5.8.0/run/cloudera-scm-server/--no-create-home--shell=/bin/false--comment&quot;ClouderaSCMUser&quot;cloudera-scm准备Parcels,用以安装CDH5将CHD5相关的Parcel包放到主节点的/opt/cloudera/parcel-repo/目录中(parcel-repo如果没有需要手动创建,)。相关的文件如下:CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcelCDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel.shamanifest.json最后将CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel.sha1,重命名为CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel.sha,这点必须注意,否则,系统会重新下载CDH-5.8.0-1.cdh5.8.0.p0.12-el7.parcel文件。相关启动脚本通过/opt/cm-5.8.0/etc/init.d/cloudera-scm-serverstart启动服务端。通过/opt/cm-5.8.0/etc/init.d/cloudera-scm-agentstart启动Agent服务。注意:如果AGent启动失败,则手动创建一个cloudera-scm-agent文件夹:mkdir-p/opt/cm-5.8.0/run/cloudera-scm-agent/然后再使用命令启动agent我们启动的其实是个service脚本,需要停止服务将以上的start参数改为stop就可以了,重启是restart。CDH5的安装配置ClouderaManagerServer和Agent都启动以后,就可以进行CDH5的安装配置了。这时可以通过浏览器访问主节点的7180端口测试一下了(由于CMServer的启动需要花点时间,这里可能要等待一会才能访问),默认的用户名和密码均为admin:选择免费版本:指定集群主机:在搜索框中填写所有的集群的Hostname或者IP,搜索如果所有的主机的agent都启动成功则会有下图显示,没有则重新启动Agent,或者检查日志报错:如果parcel文件事先放在指定文件夹中,则会找到相应的版本:注:如果CDH的安装目录不在/opt下,则需要点击更多选项,修改parcel目录及本地parcel存储路径为正确的目录,然后重启agent和server即可点击,继续,如果配置本地Parcel包无误,那么下图中的已下载,应该是瞬间就完成了,然后就是耐心等待分配过程就行了,大约10多分钟吧,取决于内网网速。注意:在安装过程中如果一直卡在“正在激活”过程中,检查相应节点的/opt/cm-5.8.0/log/cloudera-scm-agent下的日志,如果出现connectionrefuse的错误信息。则可以通过将已完成激活的主机上的parcels文件夹用scp命令复制到出问题的agent相应位置上,再在parcels文件夹下创建一个快捷方式:ln-sCDHCDH-5.8.0-1.cdh5.8.0.p0.42/然后分配就完成了。Cloudera建议将/proc/sys/vm/swappiness设置为10。当前设置为60。使用sysctl命令在运行时更改该设置并编辑/etc/sysctl.conf以在重启后保存该设置。您可以继续进行安装,但可能会遇到问题,ClouderaManager报告您的主机由于交换运行状况不佳。以下主机受到影响通过echo10&gt;/proc/sys/vm/swappiness即可解决。已启用透明大页面压缩,可能会导致重大性能问题。请运行“echonever&gt;/sys/kernel/mm/transparent_hugepage/defrag”以禁用此设置,然后将同一命令添加到/etc/rc.local等初始脚本中,以便在系统重启时予以设置。通过echonever&gt;/sys/kernel/mm/transparent_hugepage/defrag解决检查全部通过,点击继续,进入集群设置,选择自定义服务,可以根据自己需要配置安装相应的服务:根据自己的需要进行修改配置:数据库配置:开始安装启动相应组件:等待集群配置安装完成!注意:倘若过程中图形化安装过程中失误导致安装失败,需要重新图形化安装:

开发项目专栏
SpringBoot+Thymleaf项目初入(二) - 配置基础页面访问

1.建基础包/文件夹2.application.propertis编写application.propertis基础配置和数据库连接3.index.ftl注:在index.ftl页面可以输入感叹号!,然后按tab键一键生成HTML代码4.IndexController.java5.启动服务启动服务,后再浏览器输入http://localhost:8080,是否能成功访问:

开发项目专栏
SpringBoot+Thymleaf项目初入(四) - 用户登录页面优化

1.新建CSS和JS文件2.新建静态资源配置类:ImsConfig.javapackagecn.coralcloud.ims.config;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/***@authorc-geff*@nameImsConfig*@description*@date2020-11-0315:28*/@ConfigurationpublicclassImsConfigextendsWebMvcConfigurerAdapter{@OverridepublicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){registry.addResourceHandler(&quot;/static/**&quot;).addResourceLocations(&quot;classpath:/static/&quot;);super.addResourceHandlers(registry);}}注:新建config包,然后新建ImsConfig类,继承自WebMvcConfigurerAdapter,实现了addResourceHandlers方法,该方法设置了访问/static/路径的文件时会映射到项目static文件夹下3.login.ftl修改,head添加CSS引入:4.login.ftl修改,页面布局修改:5.编写对应的main.css文件,该文件为通用样式html,body{padding:0;margin:0;}.ims-form-label{font-size:15px;color:rgba(0,0,0,.7);width:100px;height:40px;line-height:40px;letter-spacing:3px;}.ims-form-input{height:40px;line-height:40px;flex:1;outline:0;padding:015px;border:1pxsolid#DCDFE6;border-radius:4px;}.ims-form-input:focus{border-color:#409EFF;}.ims-form-item{margin:15px0;width:100%;display:flex;}.ims-button{height:40px;line-height:40px;border-radius:4px;padding:030px;border:none;color:#FFFFFF;font-size:14px;background-color:#409EFF;cursor:pointer;margin:15px0;outline:0;}.ims-button:hover{background-color:#3888e0;}.ims-button:focus{background-color:rgba(64,158,255,0.81);}6.编写对应的login.css文件,该文件为登录页专用样式.login-container{display:flex;align-content:center;justify-content:center;text-align:center;-webkit-box-pack:center;-webkit-box-align:center;align-items:center;width:100vw;height:100vh;background-image:url(&quot;/static/images/login_bg.jpg&quot;);background-repeat:no-repeat;background-size:100%100%;}.login-containerform{width:350px;height:300px;background-color:rgba(255,255,255,.7);border-radius:5px;padding:10px20px;}.login-container.ims-button{width:100%}.errormsg{color:#ed2322;font-size:13px;margin-bottom:0;}7.static目录下新建文件夹images保存背景图片login_bg.jpglogin_bg.jpg