Commit b9da2c02 by aye

代码提交

parents
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>wsxt</artifactId>
<groupId>com.jty</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>auth-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>10.2.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>10.2.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>aliyun-openservices</artifactId>
<version>1.0.12</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package com.jty.nrsc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* 权限验证服务器启动类
*
* @author manjiajie
* @since 2019-6-10 20:59:28
*/
@SpringBootApplication
@EnableEurekaClient
@EnableAsync
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class,args);
}
}
package com.jty.nrsc.application;
import com.jty.nrsc.application.service.AuthRoleServer;
import com.jty.nrsc.application.service.AuthUserServer;
import com.jty.nrsc.application.service.NrscAuthorityServer;
import com.jty.nrsc.application.service.NrscTeacherService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 应用Service组件注册
*
* @author yangqijie
* @since 2019/5/31 09:57
*/
@Component
public class ApplicationRegistry implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static NrscTeacherService nrscTeacherService(){
return applicationContext.getBean(NrscTeacherService.class);
}
public static AuthUserServer authUserServer(){
return applicationContext.getBean(AuthUserServer.class);
}
public static AuthRoleServer authRoleServer(){
return applicationContext.getBean(AuthRoleServer.class);
}
public static NrscAuthorityServer nrscAuthorityServer(){
return applicationContext.getBean(NrscAuthorityServer.class);
}
@Override
public synchronized void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(ApplicationRegistry.applicationContext==null) {
ApplicationRegistry.applicationContext = applicationContext;
}
}
}
/**
* DDD: application 应用层
* 相对于领域层,应用层是很薄的一层,应用层定义了软件要完成的任务,要尽量简单.
* 它不包含任务业务规则或知识, 为下一层的领域对象协助任务、委托工作。
* 它没有反映业务情况的状态,但它可以具有反映用户或程序的某个任务的进展状态。
* 对外 为展现层提供各种应用功能(service)。
* 对内 调用领域层(领域对象或领域服务)完成各种业务逻辑任务(task)。
* 这一层也很适合写一些任务处理,日志监控
**/
package com.jty.nrsc.application;
package com.jty.nrsc.application.service;
import org.springframework.security.core.GrantedAuthority;
import java.util.List;
/**
* 权限Service
*
* @author Jason
* @since 2019/1/2 14:47
*/
public interface AuthAuthorityService {
List<? extends GrantedAuthority> getAuthority(Integer userId);
}
package com.jty.nrsc.application.service;
import com.jty.nrsc.domain.model.auth.role.AuthRole;
import com.jty.nrsc.interfaces.dto.AuthRoleDto;
import org.springframework.data.domain.Page;
/**
* AuthRoleServer
*
* @author Manjiajie
* @since 2019-6-13 11:02:21
*/
public interface AuthRoleServer {
/**通过系统识别码查找对应的角色分页列表
* @param platform systemCode
* @param roleDto roleDto
* @return Page
*/
Page<AuthRole> findPageRolesByPlatform(String platform, AuthRoleDto roleDto);
/**
* 新增角色
* @param roleDto dto
* @param platform 平台
*/
void addRole(AuthRoleDto roleDto, String platform);
/**
* 修改角色权限
* @param roleDto dto
* @param platform 平台
*/
void updateRole(AuthRoleDto roleDto,String platform);
/**
* 获取一个角色详情
* @param roleId 角色id
* @return AuthRole
*/
AuthRole getRoleById(Integer roleId);
}
package com.jty.nrsc.application.service;
import com.jty.nrsc.domain.model.auth.history.AuthUserHistory;
import com.jty.nrsc.interfaces.dto.AuthUserHistoryDto;
import com.jty.nrsc.interfaces.dto.OperatorDto;
import org.springframework.data.domain.Page;
/**
* AuthUserServer
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
public interface AuthUserServer {
void disableUser(Integer managerId, OperatorDto operatorDto);
void enableUser(Integer managerId, OperatorDto operatorDto);
String resetPassword(Integer userId);
void changePassword(String newPassword,Integer userId);
Page<AuthUserHistory> findHistoryByManagerId(AuthUserHistoryDto userHistoryDto);
}
package com.jty.nrsc.application.service;
import com.jty.nrsc.domain.model.auth.authority.nrsc.NrscAuthority;
import java.util.List;
/**
* NrscAuthorityServer
*
* @author Manjiajie
* @since 2019-6-13 11:41:59
*/
public interface NrscAuthorityServer {
/**
*获取authorities
* @return List
*/
List<NrscAuthority> getAllAuthorities();
/**
*获取depth为1的权限列表
* @return List
*/
List<NrscAuthority> getDepthOneAuthorities();
}
package com.jty.nrsc.application.service;
import com.jty.nrsc.domain.model.auth.user.nrsc.NrscTeacher;
import com.jty.nrsc.interfaces.dto.ChangePasswordDto;
import com.jty.nrsc.interfaces.dto.NrscTeacherDto;
import com.jty.nrsc.interfaces.dto.NrscTeacherSearchDto;
import com.jty.nrsc.interfaces.dto.OperatorDto;
import org.springframework.data.domain.Page;
/**
* UserService
*
* @author Manjiajie
* @since 2019-6-12 09:44:53
*/
public interface NrscTeacherService {
/**
* 获取内容生产老师列表
* @param searchDto searchDto
* @return Page
*/
Page<NrscTeacher> getNrscTeachers(NrscTeacherSearchDto searchDto);
/**
* 新增nrsc老师
* @param nrscTeacherDto nrscTeacherDto
*/
void saveNrscTeacher(NrscTeacherDto nrscTeacherDto);
/**
* 更新nrsc老师
* @param nrscTeacherDto nrscTeacherDto
*/
void updateNrscTeacher(NrscTeacherDto nrscTeacherDto);
/**
* 重置密码
* @param userId userId
*/
String resetPassword(Integer userId);
/**
*
* @param newPassword
* @param userId
*/
void changePassword(String newPassword,Integer userId);
/**
* 启用
* @param tercherId tercherId
* @param
*/
void enableNrscTeacher(Integer tercherId, OperatorDto operatorDto);
/**
* 禁用
* @param tercherId tercherId
*/
void disableNrscTeacher(Integer tercherId, OperatorDto operatorDto);
}
package com.jty.nrsc.application.service.impl;
import com.jty.nrsc.application.service.AuthAuthorityService;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.authority.AuthAuthority;
import com.jty.nrsc.domain.model.auth.role.AuthRole;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
/**
* 权限service实现类
*
* @author Jason
* @since 2019/1/2 14:47
*/
@Service
public class AuthAuthorityServiceImpl implements AuthAuthorityService {
@Override
public List<? extends GrantedAuthority> getAuthority(Integer userId) {
List<AuthAuthority> authorities= DomainRegistry.authAuthorityRepository().findAll(createByUserId(userId));
List<GrantedAuthority> authorityList=new ArrayList<>();
authorities.stream().forEach(authAuthority ->
authorityList.add(new SimpleGrantedAuthority(authAuthority.getCode()))
);
return authorityList;
}
private static Specification<AuthAuthority> createByUserId(Integer userId) {
return new Specification<AuthAuthority>() {
public Predicate toPredicate(Root<AuthAuthority> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicate = new ArrayList<>();
Join<AuthAuthority, AuthRole> roleJoin = root.join("roles", JoinType.INNER);
Join<AuthRole, AuthUser> userJoin = roleJoin.join("users", JoinType.INNER);
predicate.add(cb.equal(userJoin.get("id"),userId));
Predicate[] pre = new Predicate[predicate.size()];
return query.distinct(true).where(predicate.toArray(pre)).getRestriction();
}
};
}
}
package com.jty.nrsc.application.service.impl;
import com.jty.nrsc.application.service.AuthRoleServer;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.authority.AuthAuthority;
import com.jty.nrsc.domain.model.auth.role.AuthRole;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import com.jty.nrsc.interfaces.dto.AuthRoleDto;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import java.util.Iterator;
import java.util.List;
/**
* AuthRoleServerImpl
*
* @author Manjiajie
* @since 2019-6-13 11:03:18
*/
@Service
public class AuthRoleServerImpl implements AuthRoleServer {
private static int admin_role_id = 1;
@Override
public Page<AuthRole> findPageRolesByPlatform(String platform, AuthRoleDto roleDto) {
AuthRole authRole = new AuthRole();
authRole.setPlatform(platform);
return DomainRegistry.roleRepository().findAll( Example.of(authRole),roleDto.getPageable());
}
@Override
public void addRole(AuthRoleDto roleDto, String platform) {
AuthRole authRole = DomainRegistry.roleRepository().findByNameAndPlatform(roleDto.getName(),platform).orElse(null);
if(authRole != null){
throw new BusinessException(ResultCode.ROLE_HAS_EXIST);
}
authRole = new AuthRole();
authRole.setName(roleDto.getName());
authRole.setPlatform(platform);
authRole.save();
}
@Override
public void updateRole(AuthRoleDto roleDto, String platform) {
AuthRole authRoleById = DomainRegistry.roleRepository().findById(roleDto.getId()).orElseThrow(()->new BusinessException(ResultCode.DATA_IS_WRONG));
if(roleDto.getId() == 1){
if(CollectionUtils.isEmpty(roleDto.getAuthAuthoritiesIds())){
throw new BusinessException(ResultCode.ADMIN_AUTH_NULL);
}
}
AuthAuthority.cleanAuthority(authRoleById);
if(CollectionUtils.isNotEmpty(roleDto.getAuthAuthoritiesIds())){
authRoleById.addAuthoritiesByIds(roleDto.getAuthAuthoritiesIds());
}
authRoleById.save();
}
@Override
public AuthRole getRoleById(Integer roleId) {
return DomainRegistry.roleRepository().findById(roleId).orElseThrow(()->new BusinessException(ResultCode.DATA_IS_WRONG));
}
}
package com.jty.nrsc.application.service.impl;
import com.jty.nrsc.application.service.AuthUserServer;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.history.*;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import com.jty.nrsc.infrastructure.DomainEventPublisher;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import com.jty.nrsc.interfaces.dto.AuthUserHistoryDto;
import com.jty.nrsc.interfaces.dto.OperatorDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
/**
* AuthUserServerImpl
*
* @author Manjiajie
* @since 2019-6-12 15:34:47
*/
@Service
public class AuthUserServerImpl implements AuthUserServer {
private final PasswordEncoder passwordEncoder;
private final TokenStore tokenStore;
@Autowired
public AuthUserServerImpl(PasswordEncoder passwordEncoder, TokenStore tokenStore) {
this.passwordEncoder = passwordEncoder;
this.tokenStore = tokenStore;
}
@Override
public String resetPassword(Integer userId) {
String password = "123456";
AuthUser user = this.findById(userId);
user.setPassword(passwordEncoder.encode(password));
this.removeToken(user);
user.save();
return password;
}
@Override
public void enableUser(Integer managerId, OperatorDto operatorDto) {
DomainEventPublisher.instance().reset();
DomainEventPublisher.instance().subscriber(new EnableUserSubscriber());
AuthUser user = this.findById(managerId);
user.setOperatorDto(operatorDto);
user.enableUser();
user.save();
}
@Override
public void disableUser(Integer managerId, OperatorDto operatorDto) {
DomainEventPublisher.instance().reset();
DomainEventPublisher.instance().subscriber(new DisableUserSubscriber());
AuthUser user = this.findById(managerId);
user.setOperatorDto(operatorDto);
user.disableUser();
this.removeToken(user);
user.save();
}
@Override
public void changePassword(String newPassword, Integer userId) {
AuthUser authUser = DomainRegistry.userRepository().findById(userId).orElseThrow(()->new BusinessException(ResultCode.USER_NOT_EXIST));
authUser.setPassword(passwordEncoder.encode(newPassword));
authUser.save();
}
@Override
public Page<AuthUserHistory> findHistoryByManagerId(AuthUserHistoryDto userHistoryDto){
AuthUserHistory authUserHistory= new AuthUserHistory();
authUserHistory.setManagerId(new ManagerId(userHistoryDto.getManagerId()));
userHistoryDto.setOrderBy("modifiedTime");
userHistoryDto.setDirection("desc");
return DomainRegistry.userHistoryRepository().findAll( Example.of(authUserHistory),userHistoryDto.getPageable());
}
private AuthUser findById(Integer id){
return DomainRegistry.userRepository().findById(id).orElseThrow(()->new BusinessException(ResultCode.USER_NOT_EXIST));
}
private void removeToken(AuthUser user){
tokenStore.findTokensByClientIdAndUserName("*",user.getMobile()).forEach(tokenStore::removeAccessToken );
}
}
package com.jty.nrsc.application.service.impl;
import com.jty.nrsc.application.service.NrscAuthorityServer;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.authority.nrsc.NrscAuthority;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 说明
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
@Service
public class NrscAuthorityServerImple implements NrscAuthorityServer {
@Override
public List<NrscAuthority> getAllAuthorities() {
return DomainRegistry.nrscAuthorityRepository().findAll();
}
@Override
public List<NrscAuthority> getDepthOneAuthorities() {
return DomainRegistry.nrscAuthorityRepository().findAllByDepth(1);
}
}
package com.jty.nrsc.application.service.impl;
import com.jty.nrsc.application.ApplicationRegistry;
import com.jty.nrsc.application.service.NrscTeacherService;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.role.AuthRole;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import com.jty.nrsc.domain.model.auth.user.nrsc.NrscTeacher;
import com.jty.nrsc.domain.model.stage.BasicStage;
import com.jty.nrsc.domain.model.subject.BasicSubject;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import com.jty.nrsc.interfaces.dto.ChangePasswordDto;
import com.jty.nrsc.interfaces.dto.NrscTeacherDto;
import com.jty.nrsc.interfaces.dto.NrscTeacherSearchDto;
import com.jty.nrsc.interfaces.dto.OperatorDto;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.query.criteria.internal.OrderImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* UserServiceImpl
*
* @author Manjiajie
* @since 2019-6-12 09:58:46
*/
@Service
public class NrscTeacherServiceImpl implements NrscTeacherService {
private final PasswordEncoder passwordEncoder;
@Autowired
public NrscTeacherServiceImpl(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public Page<NrscTeacher> getNrscTeachers(NrscTeacherSearchDto searchDto) {
return DomainRegistry.nrscTeacherRepository().findAll(NrscTeacherDsl.getNrscTeachersWhereClause(searchDto),searchDto.getPageable());
}
@Override
public void saveNrscTeacher(NrscTeacherDto nrscTeacherDto) {
NrscTeacher nrscTeacher;
AuthUser user = DomainRegistry.userRepository().findByAccount(nrscTeacherDto.getAccount()).orElse(null);
if(user != null) {
nrscTeacher = DomainRegistry.nrscTeacherRepository().findById(user.getId()).orElse(null);
if(nrscTeacher != null) {
throw new BusinessException(ResultCode.USER_HAS_EXISTED);
}
}
else {
user = new AuthUser();
user.setAccount(nrscTeacherDto.getAccount());
user.setEnabled(true);
user.setPassword(passwordEncoder.encode("123456"));
if(CollectionUtils.isNotEmpty(nrscTeacherDto.getRoleIds())) {
user.addNrscRolesByIds(nrscTeacherDto.getRoleIds());
}
}
user.setRealName(nrscTeacherDto.getUsername());
user.save();
nrscTeacher = new NrscTeacher();
nrscTeacher.setId(user.getId());
nrscTeacher.setAuthUser(user);
nrscTeacher.updateStageId(nrscTeacherDto.getStageId());
nrscTeacher.updateSubjectId(nrscTeacherDto.getSubjectId());
nrscTeacher.save();
}
@Override
public void updateNrscTeacher(NrscTeacherDto nrscTeacherDto) {
NrscTeacher nrscTeacher = DomainRegistry.nrscTeacherRepository().findById(nrscTeacherDto.getId()).orElseThrow(()->new BusinessException(ResultCode.USER_NOT_EXIST));
AuthUser authUser=nrscTeacher.getAuthUser();
if(StringUtils.isNotEmpty(nrscTeacherDto.getUsername())){
authUser.setRealName(nrscTeacherDto.getUsername());
}
if(nrscTeacherDto.getSubjectId()!=null){
BasicSubject subject = new BasicSubject();
subject.setId(nrscTeacherDto.getSubjectId());
nrscTeacher.setSubject(subject);
}
if(nrscTeacherDto.getStageId()!=null){
BasicStage stage = new BasicStage();
stage.setId(nrscTeacherDto.getStageId());
nrscTeacher.setStage(stage);
}
AuthRole.cleanRole(authUser);
if(CollectionUtils.isNotEmpty(nrscTeacherDto.getRoleIds())) {
authUser.addNrscRolesByIds(nrscTeacherDto.getRoleIds());
}
nrscTeacher.save();
}
@Override
public void changePassword(String newPassword,Integer userId) {
ApplicationRegistry.authUserServer().changePassword(newPassword,userId);
}
@Override
public String resetPassword(Integer userId) {
return ApplicationRegistry.authUserServer().resetPassword(userId);
}
@Override
public void enableNrscTeacher(Integer teacherId, OperatorDto operatorDto) {
ApplicationRegistry.authUserServer().enableUser(teacherId,operatorDto);
}
@Override
public void disableNrscTeacher(Integer teacherId, OperatorDto operatorDto) {
ApplicationRegistry.authUserServer().disableUser(teacherId,operatorDto);
}
}
class NrscTeacherDsl {
public static Specification<NrscTeacher> getNrscTeachersWhereClause(NrscTeacherSearchDto searchDto) {
return new Specification<NrscTeacher>() {
@Override
public Predicate toPredicate(Root<NrscTeacher> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
List<Predicate> predicate = new ArrayList<>();
Join<NrscTeacher, BasicStage> stageJoin = root.join("stage", JoinType.INNER);
if(searchDto.getStageId() != null) {
predicate.add(cb.equal(stageJoin.get("id"), searchDto.getStageId()));
}
Join<NrscTeacher, BasicSubject> subjectJoin = root.join("subject", JoinType.INNER);
if(searchDto.getSubjectId() != null) {
predicate.add(cb.equal(subjectJoin.get("id"), searchDto.getSubjectId()));
}
Join<NrscTeacher, AuthUser> userJoin = root.join("authUser", JoinType.INNER);
if(searchDto.getEnabled() != null) {
predicate.add(cb.equal(userJoin.get("enabled"), searchDto.getEnabled()));
}
if(StringUtils.isNotEmpty(searchDto.getName())) {
predicate.add(cb.like(userJoin.get("realName"), "%" + searchDto.getName() + "%"));
}
Join<AuthUser, AuthRole> roleJoin = userJoin.join("roles", JoinType.INNER);
if(searchDto.getRoleId() != null) {
predicate.add(cb.equal(roleJoin.get("id"), searchDto.getRoleId()));
}
List<Order> orders = new ArrayList<>();
orders.add(new OrderImpl(root.get("lastLoginTime")));
orders.add(new OrderImpl(root.get("id")));
Predicate[] pre = new Predicate[predicate.size()];
return query.distinct(true).where(predicate.toArray(pre)).orderBy(orders).getRestriction();
}
};
}
}
package com.jty.nrsc.domain;
import com.jty.nrsc.domain.model.auth.authority.AuthAuthorityRepository;
import com.jty.nrsc.domain.model.auth.authority.nrsc.NrscAuthorityRepository;
import com.jty.nrsc.domain.model.auth.client.ClientRepository;
import com.jty.nrsc.domain.model.auth.history.AuthUserHistoryRepository;
import com.jty.nrsc.domain.model.auth.role.RoleRepository;
import com.jty.nrsc.domain.model.auth.user.UserRepository;
import com.jty.nrsc.domain.model.auth.user.nrsc.NrscTeacher;
import com.jty.nrsc.domain.model.auth.user.nrsc.NrscTeacherRepository;
import com.jty.nrsc.domain.model.stage.BasicStageRepository;
import com.jty.nrsc.domain.model.subject.BasicSubjectRepository;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 组件注册
*
* @author jason
* @since 2019/5/20
*/
@Component
public class DomainRegistry implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static NrscAuthorityRepository nrscAuthorityRepository(){
return applicationContext.getBean(NrscAuthorityRepository.class);
}
public static AuthAuthorityRepository authAuthorityRepository(){
return applicationContext.getBean(AuthAuthorityRepository.class);
}
public static RoleRepository roleRepository(){
return applicationContext.getBean(RoleRepository.class);
}
public static UserRepository userRepository(){
return applicationContext.getBean(UserRepository.class);
}
public static ClientRepository clientRepository(){
return applicationContext.getBean(ClientRepository.class);
}
public static BasicStageRepository stageRepository(){
return applicationContext.getBean(BasicStageRepository.class);
}
public static BasicSubjectRepository subjectRepository(){
return applicationContext.getBean(BasicSubjectRepository.class);
}
public static NrscTeacherRepository nrscTeacherRepository(){
return applicationContext.getBean(NrscTeacherRepository.class);
}
public static AuthUserHistoryRepository userHistoryRepository(){
return applicationContext.getBean(AuthUserHistoryRepository.class);
}
@Override
public synchronized void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(DomainRegistry.applicationContext==null) {
DomainRegistry.applicationContext = applicationContext;
}
}
}
package com.jty.nrsc.domain.model.auth.authority;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.jty.nrsc.domain.model.auth.role.AuthRole;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import com.jty.nrsc.domain.shared.IdentifiedValueObject;
import lombok.Data;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.*;
import java.util.Iterator;
import java.util.List;
/**
* 权限
*
* @author Manjiajie
* @since 2019-6-12 11:02:11
*/
@Data
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="platform",discriminatorType= DiscriminatorType.STRING)
@DiscriminatorValue("authority")
public class AuthAuthority extends IdentifiedEntityObject<AuthAuthority> {
private String code;
private String name;
private String url;
@ManyToOne(fetch = FetchType.LAZY)
@NotFound(action= NotFoundAction.IGNORE)
@JoinColumn(name="parent_id",
columnDefinition="int(11) not null",
insertable=false,updatable=false,
foreignKey = @ForeignKey(name = "none",value=ConstraintMode.NO_CONSTRAINT))
@JsonIgnore
private AuthAuthority parent;
private Integer depth;
private String ordinal;
private String pathIds;
@OneToMany(mappedBy = "parent",fetch = FetchType.LAZY)
private List<AuthAuthority> children;
@ManyToMany(mappedBy = "authorities",fetch = FetchType.LAZY)
private List<AuthRole> roles;
@Override
public boolean sameIdentityAs(AuthAuthority other) {
return false;
}
public static void cleanAuthority(AuthRole authRole){
Iterator<AuthAuthority> it = authRole.getAuthorities().iterator();
while(it.hasNext()){
it.next();
it.remove();
}
}
}
package com.jty.nrsc.domain.model.auth.authority;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* AuthAuthorityRepository
*
* @author Manjiajie
* @since 2019-6-13 11:26:01
*/
public interface AuthAuthorityRepository extends JpaRepository<AuthAuthority,Integer>,JpaSpecificationExecutor<AuthAuthority> {
}
package com.jty.nrsc.domain.model.auth.authority.nrsc;
import com.jty.nrsc.domain.model.auth.authority.AuthAuthority;
import lombok.Data;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
/**
* NrscAuthority
*
* @author Manjiajie
* @since 2019-6-12 15:05:43
*/
@Entity
@Data
@DiscriminatorValue("nrsc")
public class NrscAuthority extends AuthAuthority {
}
package com.jty.nrsc.domain.model.auth.authority.nrsc;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* NrscAuthorityRepository
*
* @author Manjiajie
* @since 2019-6-11 10:14:38
*/
public interface NrscAuthorityRepository extends JpaRepository<NrscAuthority,Integer> {
List<NrscAuthority> findAllByDepth(Integer depth);
}
package com.jty.nrsc.domain.model.auth.client;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import lombok.Data;
import javax.persistence.Entity;
/**
* Spring Security Oauth2中的Client
*
* @author Manjiajie
* @since 2019-6-12 11:01:58
*/
@Entity
@Data
public class AuthClient extends IdentifiedEntityObject<AuthClient> {
private String clientId;
private String clientSecret;
private String clientDecodeSecret;
private String authorizedGrantTypes;
private String scope;
private Integer userId;
@Override
public boolean sameIdentityAs(AuthClient other) {
return false;
}
}
package com.jty.nrsc.domain.model.auth.client;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
/**
* ClientRepository
*
* @author Manjiajie
* @since 2019-6-11 10:14:45
*/
public interface ClientRepository extends JpaRepository<AuthClient,Integer> {
Optional<AuthClient> findByUserId(Integer id);
Optional<AuthClient> findByClientId(String clientId);
}
package com.jty.nrsc.domain.model.auth.history;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import lombok.Data;
import javax.persistence.Embedded;
import javax.persistence.Entity;
/**
* 说明
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
@Data
@Entity
public class AuthUserHistory extends IdentifiedEntityObject<AuthUserHistory> {
private Integer operatorId;
/**
* 操作人的名字
*/
private String operatorName;
/**
* 被操作对象id
*/
@Embedded
private ManagerId managerId;
/**
* 操作项
*/
private String operation;
/**
* 操作原因
*/
private String operateReason;
@Override
public boolean sameIdentityAs(AuthUserHistory other) {
return false;
}
public void save(){
DomainRegistry.userHistoryRepository().save(this);
}
}
package com.jty.nrsc.domain.model.auth.history;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* AuthUserHistoryRepository
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
public interface AuthUserHistoryRepository extends JpaRepository<AuthUserHistory,Integer> {
}
package com.jty.nrsc.domain.model.auth.history;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.user.DisableUserEvent;
import com.jty.nrsc.infrastructure.DomainEventSubscriber;
/**
* DisableUserSubscriber
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
public class DisableUserSubscriber extends DomainEventSubscriber<DisableUserEvent> {
@Override
public Class<?> subscribedToEventType() {
return DisableUserEvent.class;
}
@Override
public void handleEvent(DisableUserEvent domainEvent) {
AuthUserHistory history = new AuthUserHistory();
history.setManagerId(domainEvent.getManagerId());
history.setOperatorId(domainEvent.getOperatorDto().getOperatorId());
history.setOperatorName(domainEvent.getOperatorDto().getOperatorName());
history.setOperateReason(domainEvent.getOperatorDto().getReason());
history.setOperation("禁用");
history.save();
}
}
package com.jty.nrsc.domain.model.auth.history;
import com.jty.nrsc.domain.model.auth.user.DisableUserEvent;
import com.jty.nrsc.domain.model.auth.user.EnableUserEvent;
import com.jty.nrsc.infrastructure.DomainEventSubscriber;
/**
* 说明
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
public class EnableUserSubscriber extends DomainEventSubscriber<EnableUserEvent> {
@Override
public Class<?> subscribedToEventType() {
return EnableUserEvent.class;
}
@Override
public void handleEvent(EnableUserEvent domainEvent) {
AuthUserHistory history = new AuthUserHistory();
history.setManagerId(domainEvent.getManagerId());
history.setOperatorId(domainEvent.getOperatorDto().getOperatorId());
history.setOperatorName(domainEvent.getOperatorDto().getOperatorName());
history.setOperateReason(domainEvent.getOperatorDto().getReason());
history.setOperation("启用");
history.save();
}
}
package com.jty.nrsc.domain.model.auth.history;
import com.jty.nrsc.domain.shared.ValueObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Embeddable;
/**
* ManagerId
*
* @author Manjiajie
* @since 2019-6-12 16:16:02
*/
@Data
@Embeddable
@AllArgsConstructor
@NoArgsConstructor
public class ManagerId implements ValueObject<ManagerId> {
@Column(name="manager_id")
private Integer id;
@Override
public boolean sameValueAs(ManagerId other) {
return false;
}
}
package com.jty.nrsc.domain.model.auth.history;
import com.jty.nrsc.domain.shared.ValueObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Embeddable;
/**
* OperatorId
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
@Data
@Embeddable
@AllArgsConstructor
@NoArgsConstructor
public class OperatorId implements ValueObject<OperatorId> {
@Column(name="operator_id")
private Integer id;
@Override
public boolean sameValueAs(OperatorId other) {
return false;
}
}
package com.jty.nrsc.domain.model.auth.role;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import com.jty.nrsc.domain.model.auth.authority.AuthAuthority;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import com.jty.nrsc.domain.shared.IdentifiedValueObject;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 用户角色
*
* @author Manjiajie
* @since 2019-6-12 11:01:42
*/
@Entity
@Data
public class AuthRole extends IdentifiedEntityObject<AuthRole> {
private String name;
private Integer parentId=0;
private Integer depth=1;
private Integer ordinal=1;
private String pathIds="";
private String platform="";
private String dtype="";
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "roles")
private List<AuthUser> users;
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "auth_relation_role_authority",
joinColumns = {@JoinColumn(name = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_id")})
private List<AuthAuthority> authorities;
public void addAuthority(AuthAuthority authority){
if(authorities==null){
authorities=new ArrayList<>();
}
authorities.add( authority );
}
public void addAuthoritiesByIds( List<Integer> authAuthoritiesIds){
authAuthoritiesIds.forEach(authAuthorityId->{
AuthAuthority authAuthority = DomainRegistry.authAuthorityRepository().findById(authAuthorityId).orElseThrow(()->new BusinessException(ResultCode.DATA_IS_WRONG));
this.addAuthority(authAuthority);
});
}
@Override
public boolean sameIdentityAs(AuthRole other) {
return false;
}
public void save(){
DomainRegistry.roleRepository().save(this);
}
public static void cleanRole(AuthUser authUser){
Iterator<AuthRole> it = authUser.getRoles().iterator();
while(it.hasNext()) {
AuthRole role = it.next();
if(StringUtils.equalsIgnoreCase(role.getPlatform(), "nrsc")) {
it.remove();
}
}
}
}
package com.jty.nrsc.domain.model.auth.role;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
/**
* RoleRepository
*
* @author Manjiajie
* @since 2019-6-11 10:14:50
*/
public interface RoleRepository extends JpaRepository<AuthRole,Integer> {
Optional<AuthRole> findByNameAndPlatform(String name, String platform);
}
package com.jty.nrsc.domain.model.auth.user;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.role.AuthRole;
import com.jty.nrsc.domain.model.auth.history.ManagerId;
import com.jty.nrsc.domain.model.auth.history.OperatorId;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import com.jty.nrsc.infrastructure.DomainEventPublisher;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import com.jty.nrsc.interfaces.dto.OperatorDto;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 权限用户
*
* @author Manjiajie
* @since 2019-6-12 11:01:16
*/
@Entity
@Data
@Inheritance(strategy = InheritanceType.JOINED)
public class AuthUser extends IdentifiedEntityObject<AuthUser> {
@Transient
private OperatorDto operatorDto;
private String account;
private String mobile;
private String password;
private Boolean enabled=true;
private String realName;
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(name = "auth_relation_user_role",
joinColumns = {@JoinColumn(name = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")})
private List<AuthRole> roles=new ArrayList<>();
@Override
public boolean sameIdentityAs(AuthUser other) {
return false;
}
public void addNrscRoles(List<AuthRole> roleList){
Iterator<AuthRole> it = this.getRoles().iterator();
while(it.hasNext()){
AuthRole role=it.next();
if(StringUtils.equalsIgnoreCase(role.getPlatform(),"nrsc")){
it.remove();
}
}
roleList.forEach(role -> {
if(role.getPlatform().equals("nrsc")){
roles.add(role);
}
});
}
public void addNrscRolesByIds( List<Integer> roleIds){
List<AuthRole> roleList = new ArrayList<>();
roleIds.forEach(roleId -> {
roleList.add(DomainRegistry.roleRepository().findById(roleId).orElseThrow(() -> new BusinessException(ResultCode.DATA_IS_WRONG)));
});
this.addNrscRoles(roleList);
}
public void enableUser(){
this.setEnabled(true);
DomainEventPublisher.instance().publish(new EnableUserEvent(operatorDto,new ManagerId(this.getId())));
}
public void disableUser(){
this.setEnabled(false);
DomainEventPublisher.instance().publish(new DisableUserEvent(operatorDto,new ManagerId(this.getId())));
}
public void save(){
DomainRegistry.userRepository().save(this);
}
}
package com.jty.nrsc.domain.model.auth.user;
import com.jty.nrsc.domain.model.auth.history.ManagerId;
import com.jty.nrsc.domain.model.auth.history.OperatorId;
import com.jty.nrsc.infrastructure.DomainEvent;
import com.jty.nrsc.interfaces.dto.OperatorDto;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* DisableUserEvent
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
@Data
@AllArgsConstructor
public class DisableUserEvent implements DomainEvent {
private OperatorDto operatorDto;
private ManagerId managerId;
}
package com.jty.nrsc.domain.model.auth.user;
import com.jty.nrsc.domain.model.auth.history.ManagerId;
import com.jty.nrsc.domain.model.auth.history.OperatorId;
import com.jty.nrsc.infrastructure.DomainEvent;
import com.jty.nrsc.interfaces.dto.OperatorDto;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 启用事件
*
* @author Manjiajie
* @since 2019-2-13 19:19:46
*/
@Data
@AllArgsConstructor
public class EnableUserEvent implements DomainEvent {
private OperatorDto operatorDto;
private ManagerId managerId;
}
package com.jty.nrsc.domain.model.auth.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
/**
* UserRepository
*
* @author Manjiajie
* @since 2019-6-11 10:14:55
*/
public interface UserRepository extends JpaRepository<AuthUser,Integer> {
Optional<AuthUser> findByMobile(String username);
Optional<AuthUser> findByAccount(String username);
Optional<AuthUser> findByMobileOrAccount(String mobile, String account);
}
package com.jty.nrsc.domain.model.auth.user.nrsc;
import com.jty.nrsc.domain.DomainRegistry;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import com.jty.nrsc.domain.model.stage.BasicStage;
import com.jty.nrsc.domain.model.subject.BasicSubject;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import com.jty.nrsc.infrastructure.constants.TableConstants;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import lombok.Data;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import java.time.LocalDateTime;
/**
* NrscTeacher
*
* @author Manjiajie
* @since 2019-6-12 14:24:22
*/
@Entity
@Data
public class NrscTeacher extends IdentifiedEntityObject<NrscTeacher> {
@OneToOne
private AuthUser authUser;
@ManyToOne
@NotFound(action= NotFoundAction.IGNORE)
private BasicSubject subject;
@ManyToOne
@NotFound(action= NotFoundAction.IGNORE)
private BasicStage stage;
private LocalDateTime lastLoginTime;
@Override
public boolean sameIdentityAs(NrscTeacher other) {
return false;
}
public void updateSubjectId(Integer subjectId){
BasicSubject subject = DomainRegistry.subjectRepository().findById(subjectId).orElseThrow(()->new BusinessException(ResultCode.DATA_IS_WRONG));
this.setSubject(subject);
}
public void updateStageId(Integer stageId){
BasicStage stage = DomainRegistry.stageRepository().findById(stageId).orElseThrow(()->new BusinessException(ResultCode.DATA_IS_WRONG));
this.setStage(stage);
}
public void save(){
DomainRegistry.nrscTeacherRepository().save(this);
}
}
package com.jty.nrsc.domain.model.auth.user.nrsc;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* NrscTeacherRepository
*
* @author Manjiajie
* @since 2019-6-12 14:33:31
*/
public interface NrscTeacherRepository extends JpaRepository<NrscTeacher,Integer>, JpaSpecificationExecutor<NrscTeacher> {
}
package com.jty.nrsc.domain.model.stage;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import lombok.Data;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
/**
* BasicStage
*
* @author Manjiajie
* @since 2019-6-12 10:19:51
*/
@Entity
@Data
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE,region = "entityCache")
public class BasicStage extends IdentifiedEntityObject<AuthUser> {
/**
* 学段名称
*/
@Column(name="name")
private String name;
/**
* 简写
*/
@Column(name="short_name")
private String shortName;
/**
* 排序
*/
@Column
private Integer ordinal;
/**
* 是否使用
*/
@Column(name="is_used")
private Boolean used;
/***
* 学段值
*/
@Column
private Integer value;
@Override
public boolean sameIdentityAs(AuthUser other) {
return false;
}
}
package com.jty.nrsc.domain.model.stage;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* BasicStageRepository
*
* @author Manjiajie
* @since 2019-6-12 11:00:41
*/
public interface BasicStageRepository extends JpaRepository<BasicStage,Integer> {
}
package com.jty.nrsc.domain.model.subject;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import com.jty.nrsc.domain.shared.IdentifiedEntityObject;
import lombok.Data;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
/**
* BasicSubject
*
* @author Manjiajie
* @since 2019-6-12 10:20:03
*/
@Entity
@Data
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE,region = "entityCache")
public class BasicSubject extends IdentifiedEntityObject<AuthUser> {
/**
* 科目名称
*/
@Column(name = "name")
private String name;
/***
* 排序
*/
@Column(name = "ordinal")
private Integer ordinal;
/**
* 是否使用
*/
@Column(name="is_used")
private Boolean used;
/***
* 学段ID
*/
@Column(name = "stage_id")
private Integer stageId;
@Override
public boolean sameIdentityAs(AuthUser other) {
return false;
}
}
package com.jty.nrsc.domain.model.subject;
import com.jty.nrsc.domain.model.stage.BasicStage;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* BasicSubjectRepository
*
* @author Manjiajie
* @since 2019-6-12 11:00:35
*/
public interface BasicSubjectRepository extends JpaRepository<BasicSubject,Integer> {
}
/**
* DDD: domain 领域层
* 领域层主要负责表达业务概念,业务状态信息和业务规则。
* Domain层是整个系统的核心层,几乎全部的业务逻辑会在该层实现。
* 领域模型层主要包含以下的内容:
* 实体(Entities):具有唯一标识的对象
* 值对象(Value Objects): 无需唯一标识的对象
* 领域服务(Domain Services): 一些行为无法归类到实体对象或值对象上,本质是一些操作,而非事物
* 聚合/聚合根(Aggregates,Aggregate Roots):
聚合是指一组具有内聚关系的相关对象的集合,每个聚合都有一个root和boundary
* 工厂(Factories): 创建复杂对象,隐藏创建细节
* 仓储(Repository): 提供查找和持久化对象的方法
*/
package com.jty.nrsc.domain;
\ No newline at end of file
package com.jty.nrsc.domain.shared;
import java.io.Serializable;
/**
* 标记实体对象
*
* @author Jason
* @since 2019/4/17 10:53
*/
public interface EntityObject<T> extends Serializable {
/**
* 实体对象的比较,通过唯一标识比较
* @param other 其它实体
* @return 如果唯一标识一样就标识一样,返回true,其它false
*/
boolean sameIdentityAs(T other);
}
package com.jty.nrsc.domain.shared;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
/**
* 层超类型(第一层)
* 委派身份标识(主键)
*
* @author Jason
* @since 2019/4/17 11:00
*/
@MappedSuperclass
public abstract class IdentifiedDomainObject implements Serializable {
@Id
private int id;
protected int getId(){
return this.id;
}
protected void setId(int id){
this.id=id;
}
public IdentifiedDomainObject(){
super();
}
}
package com.jty.nrsc.domain.shared;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
/**
* 实体生成主键的父类
*
* @author Jason
* @since 2019/4/17 14:26
*/
@MappedSuperclass
@Data
public abstract class IdentifiedEntityObject<T> implements EntityObject<T> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/***
* 数据创建时间
*/
@CreationTimestamp
private LocalDateTime createdTime;
/***
* 数据修改时间
*/
@UpdateTimestamp
private LocalDateTime modifiedTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
package com.jty.nrsc.domain.shared;
import lombok.Data;
/**
* 层超类型(第二层)
* 委派身份标识(主键)
*
* @author Jason
* @since 2019/4/17 11:05
*/
@Data
public abstract class IdentifiedValueObject<T> implements ValueObject<T> {
private Integer id=-1;
}
package com.jty.nrsc.domain.shared;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
import java.io.IOException;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Map;
import java.util.Objects;
/**
* Hibernate自定义类型转化
* Map与Json的相互转化
*
* @author Jason
* @since 2019/4/17 17:17
*/
@Slf4j
public class MapJson implements UserType {
static ObjectMapper objectMapper=new ObjectMapper();
@Override
public int[] sqlTypes() {
return new int[]{Types.VARCHAR};
}
@Override
public Class returnedClass() {
return Map.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
return Objects.equals(x, y);
}
@Override
public int hashCode(Object x) throws HibernateException {
return Objects.hashCode(x);
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
String s = rs.getString(names[0]);
if (s != null) {
try {
return objectMapper.readValue(s,Map.class);
} catch (IOException e) {
log.error(e.getMessage());
return null;
}
}
return null;
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
} else {
try {
String v = objectMapper.writeValueAsString(value);
st.setString(index, v);
} catch (IOException e) {
throw new HibernateException("Exception serializing value " + value, e);
}
}
}
@Override
public Object deepCopy(Object value) throws HibernateException {
if (value == null) return null;
return value;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return null;
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return null;
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return null;
}
}
package com.jty.nrsc.domain.shared;
import java.io.Serializable;
/**
* 值对象
*
* @author Jason
* @since 2019/4/17 10:56
*/
public interface ValueObject<T> extends Serializable {
/**
* 值对象比较是通过属性比较,他们没有唯一标识
* @param other 其它值对象
* @return 如果属性一致表示一致,返回true,其它false
*/
boolean sameValueAs(T other);
}
package com.jty.nrsc.infrastructure;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
/**
* 数据同步
*
* @author manjiajie
* @since 2019-6-10 21:03:15
*/
@Component
@Slf4j
public class ApplicationCloseListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
}
}
package com.jty.nrsc.infrastructure;
/**
* 领域事件超级父类
*
* @author manjiajie
* @since 2019-6-10 21:03:40
*/
public interface DomainEvent {
}
package com.jty.nrsc.infrastructure;
import java.util.ArrayList;
import java.util.List;
/**
* 领域事件发布者
*
* @author manjiajie
* @since 2019-6-10 21:03:55
*/
public class DomainEventPublisher {
private static final ThreadLocal<List> subscribers=new ThreadLocal<>();
private static final ThreadLocal<Boolean> publishing=
new ThreadLocal<Boolean>(){
protected Boolean initialValue(){
return Boolean.FALSE;
}
};
public static DomainEventPublisher instance(){
return new DomainEventPublisher();
}
public DomainEventPublisher(){
super();
}
public <T> void publish(final T domainEvent){
if(publishing.get()){
return;
}
try{
publishing.set(Boolean.TRUE);
List<DomainEventSubscriber<T>> registeredSubscribers=subscribers.get();
if(registeredSubscribers!=null){
Class<?> eventType=domainEvent.getClass();
for(DomainEventSubscriber<T> subscriber:registeredSubscribers){
Class<?> subscribedTo=subscriber.subscribedToEventType();
if(subscribedTo==eventType||subscribedTo== DomainEvent.class){
subscriber.handleEvent(domainEvent);
}
}
}
}finally {
publishing.set(Boolean.FALSE);
}
}
public DomainEventPublisher reset(){
if(!publishing.get()){
subscribers.set(null);
}
return this;
}
public <T> void subscriber(DomainEventSubscriber<T> subscriber){
if(publishing.get()){
return;
}
List<DomainEventSubscriber<T>> registeredSubscribers=subscribers.get();
if(registeredSubscribers==null){
registeredSubscribers=new ArrayList<>();
subscribers.set(registeredSubscribers);
}
registeredSubscribers.add(subscriber);
}
}
package com.jty.nrsc.infrastructure;
/**
* 领域事件订阅者
*
* @author manjiajie
* @since 2019-6-10 21:04:08
*/
public abstract class DomainEventSubscriber<T> {
public abstract Class<?> subscribedToEventType();
public abstract void handleEvent(T domainEvent);
}
package com.jty.nrsc.infrastructure.code;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* TODO
*
* @author Jason
* @since 2019/1/9 19:40
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
package com.jty.nrsc.infrastructure.code;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* TODO
*
* @author Jason
* @since 2019/1/9 19:49
*/
public class HttpHelper {
/**
* 获取请求Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
package com.jty.nrsc.infrastructure.code;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 验证码父类
*
* @author Jason
* @since 2019/1/2 14:47
*/
public class ValidateCode implements Serializable {
private String code;
private LocalDateTime expireTime;
public ValidateCode(){
}
public ValidateCode(String code, int expireIn){
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
}
public ValidateCode(String code, LocalDateTime expireTime){
this.code = code;
this.expireTime = expireTime;
}
public boolean isExpried() {
return LocalDateTime.now().isAfter(expireTime);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public LocalDateTime getExpireTime() {
return expireTime;
}
public void setExpireTime(LocalDateTime expireTime) {
this.expireTime = expireTime;
}
}
\ No newline at end of file
package com.jty.nrsc.infrastructure.code;
import com.jty.nrsc.infrastructure.code.image.ImageCodeGenerator;
import com.jty.nrsc.infrastructure.code.sms.DefaultSmsCodeSender;
import com.jty.nrsc.infrastructure.code.sms.SmsCodeSender;
import com.jty.nrsc.infrastructure.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 验证码配置类
* 为了方便自定义以及复用
*
* @author Jason
* @since 2019/1/2 14:47
*/
@Configuration
public class ValidateCodeBeanConfig {
@Autowired
private SecurityProperties securityProperties;
/**
*
* 当不存在imageValidateCodeGenerator的bean时,用以下配置
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "imageValidateCodeGenerator")
public ValidateCodeGenerator imageValidateCodeGenerator() {
ImageCodeGenerator imageCodeGenerator = new ImageCodeGenerator();
imageCodeGenerator.setSecurityProperties(securityProperties);
return imageCodeGenerator;
}
/**
* 短信验证码发送方法
* @return
*/
@Bean
@ConditionalOnMissingBean(SmsCodeSender.class)
public SmsCodeSender smsCodeSender(){
return new DefaultSmsCodeSender();
}
}
\ No newline at end of file
package com.jty.nrsc.infrastructure.code;
import com.jty.nrsc.infrastructure.properties.SecurityConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 验证码获取Controller
*
* @author Jason
* @since 2019/1/7 17:42
*/
@RestController
public class ValidateCodeController {
@Autowired
private ValidateCodeProcessorHolder validateCodeProcessorHolder;
/**
* 创建验证码,根据验证码类型不同,调用不同的 接口实现
*
* @param request
* @param response
* @param type
* @throws Exception
*/
@GetMapping(SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/{type}")
public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type)
throws Exception {
validateCodeProcessorHolder.findValidateCodeProcessor(type).create(new ServletWebRequest(request, response));
}
}
package com.jty.nrsc.infrastructure.code;
import com.jty.nrsc.infrastructure.support.ResultCode;
import org.springframework.security.core.AuthenticationException;
/**
* 验证异常
*
* @author Jason
* @since 2019/1/2 14:47
*/
public class ValidateCodeException extends AuthenticationException {
private ResultCode resultCode;
public ValidateCodeException(String msg) {
super(msg);
}
}
/**
*
*/
package com.jty.nrsc.infrastructure.code;
import com.jty.nrsc.infrastructure.properties.SecurityProperties;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 验证码过滤器
*
* @author Jason
* @since 2019/1/2 14:47
*/
@Component("validateCodeFilter")
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
/**
* 验证码校验失败处理器
*/
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
/**
* 系统配置信息
*/
@Autowired
private SecurityProperties securityProperties;
/**
* 系统中的校验码处理器
*/
@Autowired
private ValidateCodeProcessorHolder validateCodeProcessorHolder;
/**
* 存放所有需要校验验证码的url
*/
private Map<String, ValidateCodeType> urlMap = new HashMap<>();
/**
* 验证请求url与配置的url是否匹配的工具类
*/
private AntPathMatcher pathMatcher = new AntPathMatcher();
/**
* 初始化要拦截的url配置信息
*/
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
// urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, ValidateCodeType.IMAGE);
addUrlToMap(securityProperties.getCode().getImage().getUrl(), ValidateCodeType.IMAGE);
// urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, ValidateCodeType.SMS);
addUrlToMap(securityProperties.getCode().getSms().getUrl(), ValidateCodeType.SMS);
}
/**
* 讲系统中配置的需要校验验证码的URL根据校验的类型放入map
*
* @param urlString
* @param type
*/
protected void addUrlToMap(String urlString, ValidateCodeType type) {
if (StringUtils.isNotBlank(urlString)) {
String[] urls = StringUtils.splitByWholeSeparatorPreserveAllTokens(urlString, ",");
for (String url : urls) {
urlMap.put(url, type);
}
}
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
BodyReaderHttpServletRequestWrapper wrapper=new BodyReaderHttpServletRequestWrapper(request);
ValidateCodeType type = getValidateCodeType(request);
if (type != null) {
logger.info("校验请求(" + request.getRequestURI() + ")中的验证码,验证码类型" + type);
try {
validateCodeProcessorHolder.findValidateCodeProcessor(type)
.validate(new ServletWebRequest(wrapper, response));
logger.info("验证码校验通过");
} catch (ValidateCodeException exception) {
authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
return;
}
}
chain.doFilter(wrapper, response);
}
/**
* 获取校验码的类型,如果当前请求不需要校验,则返回null
*
* @param request
* @return
*/
private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
ValidateCodeType result = null;
if (!StringUtils.equalsIgnoreCase(request.getMethod(), "get")) {
Set<String> urls = urlMap.keySet();
for (String url : urls) {
if (pathMatcher.match(url, request.getRequestURI())) {
result = urlMap.get(url);
}
}
}
return result;
}
}
package com.jty.nrsc.infrastructure.code;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 验证码生成器
*
* @author Jason
* @since 2019/1/7 17:39
*/
public interface ValidateCodeGenerator {
/**
* 生成验证码
* @param request
* @return
*/
ValidateCode generate(ServletWebRequest request);
}
package com.jty.nrsc.infrastructure.code;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 验证码处理类
*
* @author Jason
* @since 2019/1/7 17:44
*/
public interface ValidateCodeProcessor {
/**
* 验证码放入session时的前缀
*/
String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
/**
* 创建校验码
*
* @param request
* @throws Exception
*/
void create(ServletWebRequest request) throws Exception;
/**
* 校验验证码
*
* @param servletWebRequest
* @throws Exception
*/
void validate(ServletWebRequest servletWebRequest);
}
package com.jty.nrsc.infrastructure.code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 验证码处理器Holder
*
* @author Jason
* @since 2019/1/7 17:42
*/
@Component
public class ValidateCodeProcessorHolder {
@Autowired
private Map<String, ValidateCodeProcessor> validateCodeProcessors;
public ValidateCodeProcessor findValidateCodeProcessor(ValidateCodeType type) {
return findValidateCodeProcessor(type.toString().toLowerCase());
}
public ValidateCodeProcessor findValidateCodeProcessor(String type) {
String name = type.toLowerCase() + ValidateCodeProcessor.class.getSimpleName();
ValidateCodeProcessor processor = validateCodeProcessors.get(name);
if (processor == null) {
throw new ValidateCodeException("验证码处理器" + name + "不存在");
}
return processor;
}
}
package com.jty.nrsc.infrastructure.code;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 验证码权限配置
*
* @author Jason
* @since 2019/1/7 17:35
*/
public interface ValidateCodeRepository {
/**
* 保存验证码
*
* @param request
* @param code
* @param validateCodeType
*/
void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType);
/**
* 获取验证码
*
* @param request
* @param validateCodeType
* @return
*/
ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType);
/**
* 移除验证码
*
* @param request
* @param codeType
*/
void remove(ServletWebRequest request, ValidateCodeType codeType);
}
/**
*
*/
package com.jty.nrsc.infrastructure.code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.stereotype.Component;
/**
* 验证码权限配置
*
* @author Jason
* @since 2019/1/7 17:35
*/
@Component("validateCodeSecurityConfig")
public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private ValidateCodeFilter validateCodeFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
}
}
package com.jty.nrsc.infrastructure.code;
import com.jty.nrsc.infrastructure.properties.SecurityConstants;
/**
* 验证码类型枚举
*
* @author Jason
* @since 2019/1/7 17:47
*/
public enum ValidateCodeType {
/**
* 短信验证码
*/
SMS {
@Override
public String getParamNameOnValidate() {
return SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_SMS;
}
},
/**
* 图片验证码
*/
IMAGE {
@Override
public String getParamNameOnValidate() {
return SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_IMAGE;
}
};
/**
* 校验时从请求中获取的参数的名字
* @return
*/
public abstract String getParamNameOnValidate();
}
package com.jty.nrsc.infrastructure.code.image;
import com.jty.nrsc.infrastructure.code.ValidateCode;
import lombok.Data;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
/**
* 图片验证码
*
* @author Jason
* @since 2019/1/7 17:35
*/
@Data
public class ImageCode extends ValidateCode {
private BufferedImage image;
public ImageCode(){
super();
}
public ImageCode(BufferedImage image, String code, int expireIn){
super(code, expireIn);
this.image = image;
}
public ImageCode(BufferedImage image, String code, LocalDateTime expireTime){
super(code, expireTime);
this.image = image;
}
}
package com.jty.nrsc.infrastructure.code.image;
import com.jty.nrsc.infrastructure.code.ValidateCodeGenerator;
import com.jty.nrsc.infrastructure.properties.SecurityProperties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* 图片验证码生成器
*
* @author Jason
* @since 2019/1/7 17:51
*/
@Data
public class ImageCodeGenerator implements ValidateCodeGenerator {
/**
* 系统配置
*/
@Autowired
private SecurityProperties securityProperties;
@Override
public ImageCode generate(ServletWebRequest request) {
int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width",
securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request.getRequest(), "height",
securityProperties.getCode().getImage().getHeight());
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
String sRand = "";
for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
}
g.dispose();
return new ImageCode(image, sRand, securityProperties.getCode().getImage().getExpireIn());
}
/**
* 生成随机背景条纹
*
* @param fc
* @param bc
* @return
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}
/**
*
*/
package com.jty.nrsc.infrastructure.code.image;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jty.nrsc.infrastructure.code.impl.AbstractValidateCodeProcessor;
import com.jty.nrsc.infrastructure.support.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
/**
* 图片验证码处理器
*
* @author Jason
* @since 2019/1/7 17:51
*/
@Component("imageValidateCodeProcessor")
public class ImageCodeProcessor extends AbstractValidateCodeProcessor<ImageCode> {
@Autowired
private ObjectMapper objectMapper;
/**
* 发送图形验证码,将其写到响应中
*/
@Override
protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
HttpServletResponse response=request.getResponse();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(imageCode.getImage(), "jpg", outputStream);
String base64Img = Base64.getEncoder().encodeToString(outputStream.toByteArray());
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(Result.success(base64Img)));
}
}
/**
*
*/
package com.jty.nrsc.infrastructure.code.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jty.nrsc.infrastructure.code.*;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
/**
* 验证码处理器抽象类
*
* @author Jason
* @since 2019/1/2 14:47
*/
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
/**
* 收集系统中所有的 接口的实现。
*/
@Autowired
private Map<String, ValidateCodeGenerator> validateCodeGenerators;
@Autowired
private ValidateCodeRepository validateCodeRepository;
@Autowired
private ObjectMapper objectMapper;
@Override
public void create(ServletWebRequest request) throws Exception {
C validateCode = generate(request);
save(request, validateCode);
send(request, validateCode);
}
/**
* 生成校验码
*
* @param request
* @return
*/
private C generate(ServletWebRequest request) {
String type = getValidateCodeType(request).toString().toLowerCase();
String generatorName = type + ValidateCodeGenerator.class.getSimpleName();
ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName);
if (validateCodeGenerator == null) {
throw new ValidateCodeException("验证码生成器" + generatorName + "不存在");
}
return (C) validateCodeGenerator.generate(request);
}
/**
* 保存校验码
*
* @param request
* @param validateCode
*/
private void save(ServletWebRequest request, C validateCode) {
ValidateCode code = new ValidateCode(validateCode.getCode(), validateCode.getExpireTime());
validateCodeRepository.save(request, code, getValidateCodeType(request));
}
/**
* 发送校验码,由子类实现
*
* @param request
* @param validateCode
* @throws Exception
*/
protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;
/**
* 根据请求的url获取校验码的类型
*
* @param request
* @return
*/
private ValidateCodeType getValidateCodeType(ServletWebRequest request) {
String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor");
return ValidateCodeType.valueOf(type.toUpperCase());
}
@SuppressWarnings("unchecked")
@Override
public void validate(ServletWebRequest request) {
ValidateCodeType codeType = getValidateCodeType(request);
C codeInSession = (C) validateCodeRepository.get(request, codeType);
String codeInRequest;
try {
String contentType = request.getRequest().getHeaders("content-type").nextElement();
if(StringUtils.contains(contentType,"application/json")){
codeInRequest=getJsonParameter(request.getRequest(),codeType.getParamNameOnValidate());
}else {
codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
codeType.getParamNameOnValidate());
}
} catch (ServletRequestBindingException e) {
throw new ValidateCodeException("获取验证码的值失败");
}
if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException(codeType + "验证码的值不能为空");
}
if (codeInSession == null) {
throw new ValidateCodeException(codeType + "验证码不存在");
}
if (codeInSession.isExpried()) {
validateCodeRepository.remove(request, codeType);
throw new ValidateCodeException(codeType + "验证码已过期");
}
if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException(codeType + "验证码不匹配");
}
validateCodeRepository.remove(request, codeType);
}
protected String getJsonParameter(HttpServletRequest request, String paramNameOnValidate){
try {
Map<String,String> map=objectMapper.readValue(request.getInputStream(),Map.class);
return map.get(paramNameOnValidate);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
package com.jty.nrsc.infrastructure.code.impl;
import com.jty.nrsc.infrastructure.code.ValidateCode;
import com.jty.nrsc.infrastructure.code.ValidateCodeException;
import com.jty.nrsc.infrastructure.code.ValidateCodeRepository;
import com.jty.nrsc.infrastructure.code.ValidateCodeType;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.concurrent.TimeUnit;
/**
* 基于redis的验证码存取器,避免由于没有session导致无法存取验证码的问题
*/
@Component
public class RedisValidateCodeRepository implements ValidateCodeRepository {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Override
public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType) {
redisTemplate.opsForValue().set(buildKey(request, validateCodeType),
code,
10,
TimeUnit.MINUTES);
}
@Override
public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) {
Object value = redisTemplate.opsForValue().get(buildKey(request, validateCodeType));
if (value == null) {
return null;
}
return (ValidateCode) value;
}
@Override
public void remove(ServletWebRequest request, ValidateCodeType codeType) {
redisTemplate.delete(buildKey(request, codeType));
}
/**
* @param request
* @param type
* @return
*/
private String buildKey(ServletWebRequest request, ValidateCodeType type) {
String deviceId = request.getHeader("deviceId");
if (StringUtils.isBlank(deviceId)) {
throw new ValidateCodeException("请在请求头中携带deviceId参数");
}
return "code:" + type.toString().toLowerCase() + ":" + deviceId;
}
}
package com.jty.nrsc.infrastructure.code.sms;
import com.aliyuncs.exceptions.ClientException;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import lombok.extern.slf4j.Slf4j;
/**
* 手机验证码默认实现
*
* @author Jason
* @since 2019/1/7 17:35
*/
@Slf4j
public class DefaultSmsCodeSender implements SmsCodeSender {
@Override
public void send(String mobile, String code) {
System.out.println("向手机"+mobile+"发送短信验证码"+code);
try {
log.info("向手机{}发送短信验证码:{}",mobile,code);
SmsUtil.sendSms(mobile,code);
} catch (ClientException e) {
throw new BusinessException(ResultCode.SYSTEM_INNER_ERROR);
}
}
}
package com.jty.nrsc.infrastructure.code.sms;
import com.jty.nrsc.infrastructure.code.ValidateCode;
import com.jty.nrsc.infrastructure.code.ValidateCodeGenerator;
import com.jty.nrsc.infrastructure.properties.SecurityProperties;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 手机验证码生成器
*
* @author Jason
* @since 2019/1/7 17:35
*/
@Component("smsValidateCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator {
@Autowired
private SecurityProperties securityProperties;
@Override
public ValidateCode generate(ServletWebRequest request) {
String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLength());
return new ValidateCode(code, securityProperties.getCode().getSms().getExpireIn());
}
}
package com.jty.nrsc.infrastructure.code.sms;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jty.nrsc.infrastructure.code.ValidateCode;
import com.jty.nrsc.infrastructure.code.impl.AbstractValidateCodeProcessor;
import com.jty.nrsc.infrastructure.properties.SecurityConstants;
import com.jty.nrsc.infrastructure.support.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 短信验证码生成器
*
* @author Jason
* @since 2019/1/7 17:35
*/
@Component("smsValidateCodeProcessor")
public class SmsCodeProcessor extends AbstractValidateCodeProcessor<ValidateCode> {
/**
* 短信验证码发送器
*/
@Autowired
private SmsCodeSender smsCodeSender;
@Autowired
private ObjectMapper objectMapper;
@Override
protected void send(ServletWebRequest request, ValidateCode validateCode) throws Exception {
String paramName = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;
String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), paramName);
smsCodeSender.send(mobile, validateCode.getCode());
HttpServletResponse response=request.getResponse();
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(Result.success()));
}
}
/**
*
*/
package com.jty.nrsc.infrastructure.code.sms;
/**
* 短信验证码生成器
*
* @author Jason
* @since 2019/1/7 17:35
*/
public interface SmsCodeSender {
void send(String mobile, String code);
}
package com.jty.nrsc.infrastructure.code.sms;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.jty.nrsc.infrastructure.constants.AlibabaConstants;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SmsUtil {
//产品名称:云通信短信API产品,开发者无需替换
static final String product = "Dysmsapi";
//产品域名,开发者无需替换
static final String domain = "dysmsapi.aliyuncs.com";
//此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
public static SendSmsResponse sendSms(String phone,String code) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", AlibabaConstants.accessKeyId, AlibabaConstants.accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers(phone);
//必填:短信签名-可在短信控制台中找到
request.setSignName(AlibabaConstants.SMS_sign_name);
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(AlibabaConstants.SMS_template_code);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam("{\"code\":\""+code+"\"}");
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
return sendSmsResponse;
}
public static QuerySendDetailsResponse querySendDetails(String bizId) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", AlibabaConstants.accessKeyId, AlibabaConstants.accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象
QuerySendDetailsRequest request = new QuerySendDetailsRequest();
//必填-号码
request.setPhoneNumber("15801173079");
//可选-流水号
request.setBizId(bizId);
//必填-发送日期 支持30天内记录查询,格式yyyyMMdd
SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
request.setSendDate(ft.format(new Date()));
//必填-页大小
request.setPageSize(10L);
//必填-当前页码从1开始计数
request.setCurrentPage(1L);
//hint 此处可能会抛出异常,注意catch
QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
return querySendDetailsResponse;
}
public static void main(String[] args) throws ClientException, InterruptedException {
//发短信
SendSmsResponse response = sendSms("13397091205","123456");
System.out.println("短信接口返回的数据----------------");
System.out.println("Code=" + response.getCode());
System.out.println("Message=" + response.getMessage());
System.out.println("RequestId=" + response.getRequestId());
System.out.println("BizId=" + response.getBizId());
Thread.sleep(3000L);
//查明细
if(response.getCode() != null && response.getCode().equals("OK")) {
QuerySendDetailsResponse querySendDetailsResponse = querySendDetails(response.getBizId());
System.out.println("短信明细查询接口返回数据----------------");
System.out.println("Code=" + querySendDetailsResponse.getCode());
System.out.println("Message=" + querySendDetailsResponse.getMessage());
int i = 0;
for(QuerySendDetailsResponse.SmsSendDetailDTO smsSendDetailDTO : querySendDetailsResponse.getSmsSendDetailDTOs())
{
System.out.println("SmsSendDetailDTO["+i+"]:");
System.out.println("Content=" + smsSendDetailDTO.getContent());
System.out.println("ErrCode=" + smsSendDetailDTO.getErrCode());
System.out.println("OutId=" + smsSendDetailDTO.getOutId());
System.out.println("PhoneNum=" + smsSendDetailDTO.getPhoneNum());
System.out.println("ReceiveDate=" + smsSendDetailDTO.getReceiveDate());
System.out.println("SendDate=" + smsSendDetailDTO.getSendDate());
System.out.println("SendStatus=" + smsSendDetailDTO.getSendStatus());
System.out.println("Template=" + smsSendDetailDTO.getTemplateCode());
}
System.out.println("TotalCount=" + querySendDetailsResponse.getTotalCount());
System.out.println("RequestId=" + querySendDetailsResponse.getRequestId());
}
}
}
package com.jty.nrsc.infrastructure.config;
import com.jty.nrsc.infrastructure.security.MyClientDetailService;
import com.jty.nrsc.infrastructure.security.MyUserDetailService;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* 资源服务器配置类
*
* @author Jason
* @since 2018/12/14 09:04
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
private MyClientDetailService myClientDetailService;
private MyUserDetailService myUserDetailService;
private AuthenticationManager authenticationManager;
private TokenStore tokenStore;
public AuthorizationServerConfigurer(MyClientDetailService myClientDetailService, MyUserDetailService myUserDetailService, AuthenticationManager authenticationManager, TokenStore tokenStore) {
this.myClientDetailService = myClientDetailService;
this.myUserDetailService = myUserDetailService;
this.authenticationManager = authenticationManager;
this.tokenStore = tokenStore;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(myClientDetailService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(myUserDetailService)
.tokenStore(tokenStore);
}
}
package com.jty.nrsc.infrastructure.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* Feign调用配置Token转发
*
* @author Jason
* @since 2018/12/29 13:08
*/
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//添加token
template.header("Access-Token", request.getHeader("Access-Token"));
template.header("Authorization", request.getHeader("Authorization"));
}
}
package com.jty.nrsc.infrastructure.config;
import com.jty.nrsc.infrastructure.code.ValidateCodeSecurityConfig;
import com.jty.nrsc.infrastructure.security.CustomAuthenticationFilter;
import com.jty.nrsc.infrastructure.security.CustomTokenFilter;
import com.jty.nrsc.infrastructure.security.embed.EmbedAuthenticationConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 资源服务器启动配置类
*
* @author Jason
* @since 2018/12/14 10:04
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceConfigurer extends ResourceServerConfigurerAdapter {
@Autowired
protected AuthenticationSuccessHandler jtyAuthenticationSuccessHandler;
@Autowired
protected AuthenticationFailureHandler jtyAuthenticationFailureHandler;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ValidateCodeSecurityConfig validateCodeSecurityConfig;
@Autowired
private CustomTokenFilter customTokenFilter;
@Autowired
private EmbedAuthenticationConfig embedAuthenticationConfig;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.formLogin()
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")
.successHandler(jtyAuthenticationSuccessHandler)
.failureHandler(jtyAuthenticationFailureHandler)
.and()
.authorizeRequests()
.antMatchers("/authentication/require", "/api/developer", "/code/**","/api/developer/password-reset","/authentication/api","/authentication/token","/api/apply","/srb/school-configs/loginByDomain","/forget/password","/srb/commons/code/verify","/srb/teachers/register","/check-user-exist/{phone}","/add","/client/{clientDecodeSecret}")
.permitAll()
.antMatchers("/api/manage/user**")
.hasAuthority("API_MANAGE_USER")
.antMatchers("/api/manage/developer**")
.hasAuthority("API_MANAGE_DEVELOPER")
.anyRequest().authenticated();
http.addFilterAt(customAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
http.apply(validateCodeSecurityConfig);
http.apply(embedAuthenticationConfig);
http.addFilterBefore(customTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
//注册自定义的UsernamePasswordAuthenticationFilter
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(jtyAuthenticationSuccessHandler);
filter.setAuthenticationFailureHandler(jtyAuthenticationFailureHandler);
filter.setFilterProcessesUrl("/authentication/form");
filter.setAuthenticationManager(authenticationManager);
return filter;
}
}
package com.jty.nrsc.infrastructure.config;
import com.jty.nrsc.infrastructure.properties.SecurityProperties;
import com.jty.nrsc.infrastructure.security.MyRedisTokenStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore tokenStore(){
TokenStore tokenStore=new MyRedisTokenStore(redisConnectionFactory);
return tokenStore;
}
}
package com.jty.nrsc.infrastructure.config;
import com.jty.nrsc.infrastructure.security.embed.EmbedAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
/**
* 添加自定义的验证Provider
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(embedAuthenticationProvider());
auth.authenticationProvider(daoAuthenticationProvider());
}
/**
* 为了SpringCloud提供可访问的AuthenticationManager
* 见源码注释
* @return
* @throws Exception
*/
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 自定义嵌入验证Provider
* @return
*/
@Bean
public EmbedAuthenticationProvider embedAuthenticationProvider(){
EmbedAuthenticationProvider provider = new EmbedAuthenticationProvider();
return provider;
}
@Autowired
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userDetailsService);
return provider;
}
}
package com.jty.nrsc.infrastructure.constants;
/**
* 阿里产品常量
* @author Zhongwentao
* @since 2019/2/21 10:32
*/
public class AlibabaConstants {
public final static String accessKeyId = "LTAINPdmg9GsW51l";
public final static String accessKeySecret = "vXm1u7WrIMrvru8EvBkZpkYSurrh9C";
//oss 中 营业执照bucket
public final static String OSS_business_license_bucket_name = "jty-enterprise-business-license";
//oss 中 用户头像bucket
public final static String OSS_user_logo_bucket_name = "jty-user-logo";
//oss 中 文档bucket
public final static String OSS_document_bucket_name = "jty-document";
public final static String OSS_xbzyk_bucket_name = "jty-xbzyk";
//oss 的 endpoint
public final static String OSS_endpoint = "http://oss-cn-beijing.aliyuncs.com";
//SMS 短信签名
public final static String SMS_sign_name = "金太阳";
//SMS 短信模板
public final static String SMS_template_code = "SMS_159783791";
//oss 中 banner的bucket
public final static String OSS_banner_bucket_name = "jty-prefecture-banner";
}
package com.jty.nrsc.infrastructure.constants;
/**
* 通常常量
*
* @author Jason
* @since 2019/1/2 14:47
*/
public class CommonConstants {
public static final String SESSION_KEY_VALIDATE_CODE="session_key_validate_code";
public static final int school_question_pre_day_limit = 3000;
public static final int school_making_paper_pre_day_limit = 1000;
public static final int school_paper_download_pre_day_limit = 100;
public static final int teacher_question_pre_day_limit = 100;
public static final int teacher_making_paper_pre_day_limit = 30;
public static final int teacher_paper_download_pre_day_limit = 100;
public static final int teacher_document_download_pre_day_limit = 100;
public static final int school_component_tryout_limit = 10;
public static final int school_document_download_pre_day_limit = 1000;
public static final int school_teacher_limit = 100;
public static final String zipfile = "zip";
public static final String rarfile = "rar";
public static final String mp3file = "mp3";
public static final String mp4file = "mp4";
}
package com.jty.nrsc.infrastructure.constants;
/**
* Elasticsearch常量表
*
* @author Jason
* @since 2019/1/2 14:43
*/
public interface ElasticsearchConstants {
String ES_TYPE="doc";
String LOG_INTERFACE_INVOKE="log_interface_invoke";
String LOG_QUESTION_DETAIL_INVOKE="log_question_detail_invoke";
String LOG_DOCUMENT_DOWNLOAD="log_document_download";
String QUESTION="questions";
/**
* 文档索引和分组
*/
String DOCUMENT = "document";
}
package com.jty.nrsc.infrastructure.constants;
/**
* TODO
*
* @author Jason
* @since 2018/12/29 09:42
*/
public class InterfaceConstants {
public static final String INTERFACE_GET_QUESTION_DETAIL="getQuestionDetail";
public static final String INTERFACE_DOCUMENT_DOWNLOAD="getDocumentDownloadUrl";
}
package com.jty.nrsc.infrastructure.constants;
/**
*
* @author Zhongwentao
* @since 2019/1/8 11:43
*/
public class PaperConstants {
public final static String name = "未命名";
public final static String mainTitle = "2018-2019学年度xx学校xx月考卷";
public final static String subTitle = "试卷副标题";
public final static String secretSign = "绝密★启用前";
public final static String info = "考试范围:xxx;考试时间:100分钟;命题人:xxx";
public final static String studentInput = "学校:___________姓名:___________班级:___________考号:___________";
public final static String payAttention = "1. 答题前填写好自己的姓名、班级、考号等信息<br />2. 请将答案正确填写在答题卡上";
public final static String paperSection1Note = "分卷I 注释";
public final static String paperSection2Note = "分卷II 注释";
public final static String paperQuestionTypeNote = "注释";
public final static Integer paperQuestionScore = 0;
}
package com.jty.nrsc.infrastructure.constants;
/**
* 资源文档类型
* @author yizy
*/
public class RabbitMqQueueConstants {
/**
* 推送新增资源到ES通道
*/
public static final String schoolResourceQueue = "schoolResourceQueue";
/**
* 推送更新资源到ES通道
*/
public static final String updateResourceQueue = "updateResourceQueue";
/**
* 推送更新资源到ES通道
*/
public static final String senMsgQueue = "sendMsgQueue";
public static final String REGISTER = "registerQueue";
public static final String EDIT_INTEGRAL = "editIntegral";
}
package com.jty.nrsc.infrastructure.constants;
/**
*
* @author Zhongwentao
* @since 2019/1/24 13:45
*/
public class SchoolConfigConstants {
public static final String themeColor = "chengwenlv";
public static final String focusColor = "";
public static final String logoUrl = "http://jty-user-logo.oss-cn-beijing.aliyuncs.com/155420181264077239481";
public static final String[] banner = {"http://jty-user-logo.oss-cn-beijing.aliyuncs.com/155420179853739050568"};
public static final String domain = "";
public static final String mainPageUrl = "";
public static final Integer loginStyle = 1;
public static final String showNavigation = "";
public static final String address = "";
public static final String resourcePower = "";
public static final String questionPower = "";
public static final Integer resourceLevel = null;
public static final Integer questionLevel = null;
public static final Integer homeQuestionNum = null;
public static final Integer homeResourceNum = null;
public static final Integer homePaperNum = null;
public static final Integer homeVideoNum = null;
public static final String fontSize = "";
public static final String htmlTitle = "";
public static final Boolean noPageHeader = false;
public static final Boolean noPageFooter = false;
public static final String footerContent = "";
public static final Boolean noUpload = false;
public static final String needShowStages = "";
public static final String platformName = "";
public static final Boolean needReview = true;
public static final Boolean useHolidayConfig = false;
public static final Integer defaultId = 399458;
}
package com.jty.nrsc.infrastructure.constants;
/**
* 表名静态常量
* @author Zhongwentao
* @since 2018/12/27 18:14
*/
public class TableConstants {
//-----------------------------------------------
//* basic
//-----------------------------------------------
public static final String BASIC_SUBJECT = "basic_subject";
public static final String BASIC_QUESTION_TYPE = "basic_question_type";
public static final String BASIC_RELATION_QUESTION_TYPE_SUBJECT = "basic_relation_question_type_subject";
public static final String BASIC_DOCUMENT = "basic_document";
public static final String BASIC_BOOK_NODE = "basic_book_node";
//-----------------------------------------------
//* paper
//-----------------------------------------------
public static final String PAPER_QUESTION_TYPE = "paper_question_type";
public static final String PAPER_QUESTION = "paper_question";
//-----------------------------------------------
//* srb
//-----------------------------------------------
public static final String SRB_COMPONENT = "srb_component";
public static final String SRB_MANAGER = "srb_manager";
public static final String SRB_MANAGER_HISTORY = "srb_manager_history";
public static final String SRB_ORGANIZATION = "srb_organization";
public static final String SRB_ORGANIZATION_SCHOOL = "srb_organization_school";
public static final String SRB_PLATFORM_ACCESS_CONFIG = "srb_platform_access_config";
public static final String SRB_SCHOOL = "srb_school";
public static final String SRB_SCHOOL_APPLY = "srb_school_apply";
public static final String SRB_SCHOOL_CONFIG = "srb_school_config";
public static final String SRB_SUBSCRIBE_COMPONENT = "srb_subscribe_component";
public static final String SRB_SUBSCRIBE_COMPONENT_HISTORY = "srb_subscribe_component_history";
public static final String SRB_TEACHER = "srb_teacher";
public static final String SRB_TEACHER_GROUP = "srb_teacher_group";
public static final String SRB_TEACHER_IP_RECORD = "srb_teacher_ip_record";
public static final String SRB_QUESTION_FEEDBACK = "srb_question_feedback";
public static final String SRB_TEACHER_LIMIT = "srb_teacher_limit";
public static final String SRB_SCHOOL_LIMIT = "srb_school_limit";
public static final String SRB_TEACHER_SIGN = "srb_teacher_sign";
public static final String LOG_QUESTION_DETAIL_SEARCH = "log_question_detail_search";
public static final String LOG_PAPER_DOWNLOAD = "log_paper_download";
public static final String LOG_QUESTION_COLLECTION = "log_question_collection";
public static final String LOG_DOCUMENT_COLLECTION = "log_document_collection";
public static final String NRSC_TEACHER = "nrsc_teacher";
}
/**
* DDD: infrastructure 基础实施层 最底层(但与所有层进行交互)
* 向其他层提供 通用的 技术能力(比如工具类,第三方库类支持,常用基本配置,数据访问底层实现)
* 基础实施层主要包含以下的内容:
* 为应用层 传递消息(比如通知)
* 为领域层 提供持久化机制(最底层的实现)
* 为用户界面层 提供组件配置
* 基础设施层还能够通过架构框架来支持四个层次间的交互模式。
*/
package com.jty.nrsc.infrastructure;
\ No newline at end of file
/**
*
*/
package com.jty.nrsc.infrastructure.properties;
import lombok.Data;
/**
* @author zhailiang
*
*/
@Data
public class ImageCodeProperties extends SmsCodeProperties {
public ImageCodeProperties() {
setLength(4);
}
private int width = 67;
private int height = 23;
}
package com.jty.nrsc.infrastructure.properties;
public interface SecurityConstants {
/**
* 默认的处理验证码的url前缀
*/
String DEFAULT_VALIDATE_CODE_URL_PREFIX = "/code";
/**
* 当请求需要身份认证时,默认跳转的url
*
* @see
*/
String DEFAULT_UNAUTHENTICATION_URL = "/authentication/require";
/**
* 默认的用户名密码登录请求处理url
*/
String DEFAULT_LOGIN_PROCESSING_URL_FORM = "/authentication/form";
/**
* 默认的API登录请求处理url
*/
String DEFAULT_LOGIN_PROCESSING_URL_MOBILE = "/authentication/api";
/**
* 验证图片验证码时,http请求中默认的携带图片验证码信息的参数的名称
*/
String DEFAULT_PARAMETER_NAME_CODE_IMAGE = "imageCode";
/**
* 验证短信验证码时,http请求中默认的携带短信验证码信息的参数的名称
*/
String DEFAULT_PARAMETER_NAME_CODE_SMS = "smsCode";
/**
* 发送短信验证码 或 验证短信验证码时,传递手机号的参数的名称
*/
String DEFAULT_PARAMETER_NAME_MOBILE = "mobile";
/**
* 登录失败记录
*/
String SESSION_FAILED_LOGIN_COUNT="session_failed_login_count";
/**
* session失效默认的跳转地址
*/
String DEFAULT_SESSION_INVALID_URL = "/session/invalid";
}
\ No newline at end of file
package com.jty.nrsc.infrastructure.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "jty.security")
@Data
public class SecurityProperties {
private ValidateCodeProperties code = new ValidateCodeProperties();
private String loginPage="/login.html";
private boolean schoolEnable=false;
private int failCount=2;
}
/**
*
*/
package com.jty.nrsc.infrastructure.properties;
import lombok.Data;
/**
* @author zhailiang
*
*/
@Data
public class SmsCodeProperties {
private int length = 6;
private int expireIn = 600;
private String url;
}
/**
*
*/
package com.jty.nrsc.infrastructure.properties;
import lombok.Data;
/**
* @author zhailiang
*
*/
@Data
public class ValidateCodeProperties {
private ImageCodeProperties image = new ImageCodeProperties();
private SmsCodeProperties sms = new SmsCodeProperties();
}
package com.jty.nrsc.infrastructure.security;
import lombok.Getter;
import lombok.Setter;
/**
* 自定义登录用户信息
*
* @author Jason
* @since 2018/12/27 17:50
*/
@Getter
@Setter
public class AuthenticationBean {
private String username;
private String password;
}
package com.jty.nrsc.infrastructure.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义认证过滤器
* 处理传递的Json类型数据
*
* @author Jason
* @since 2018/12/27 17:49
*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if(StringUtils.equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE,request.getContentType())
||StringUtils.equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE,request.getContentType())){
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()){
AuthenticationBean authenticationBean = mapper.readValue(is,AuthenticationBean.class);
authRequest = new UsernamePasswordAuthenticationToken(
authenticationBean.getUsername(), authenticationBean.getPassword());
}catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
"", "");
}finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
else {
return super.attemptAuthentication(request, response);
}
}
}
package com.jty.nrsc.infrastructure.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.jty.nrsc.infrastructure.support.Result;
import com.jty.nrsc.infrastructure.util.HeadUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义过滤器
* 处理通过refresh_token获取新token
*
* @author Jason
* @since 2018/12/29 16:41
*/
@Component
public class CustomTokenFilter extends OncePerRequestFilter {
@Autowired
@Qualifier("defaultAuthorizationServerTokenServices")
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private ObjectMapper objectMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (StringUtils.equalsIgnoreCase(request.getRequestURI(), "/auth/authentication/token")) {
// 获取请求头中的Authorization
String header = request.getHeader("Authorization");
// 是否以Basic开头
if (header == null || !header.startsWith("Basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中无client信息");
}
String[] tokens = HeadUtils.extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
} else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
}
String refreshToken = request.getParameter("refresh_token");
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
try (InputStream is = request.getInputStream()) {
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
RefreshTokenBean refreshTokenBean = objectMapper.readValue(is, RefreshTokenBean.class);
refreshToken = refreshTokenBean.getRefreshToken();
} catch (IOException e) {
e.printStackTrace();
}
} else {
refreshToken = request.getParameter("refreshToken");
}
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,
clientId,
clientDetails.getScope(),
"custom");
OAuth2AccessToken token = authorizationServerTokenServices.refreshAccessToken(refreshToken, tokenRequest);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(Result.success(token)));
} else {
filterChain.doFilter(request, response);
}
}
}
package com.jty.nrsc.infrastructure.security;
import com.jty.nrsc.domain.model.auth.client.AuthClient;
import com.jty.nrsc.domain.model.auth.client.ClientRepository;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;
import java.util.Arrays;
/**
* 资源服务器的自定义Client配置获取
*
* @author Jason
* @since 2018/12/14 08:51
*/
@Service
public class MyClientDetailService implements ClientDetailsService {
private final ClientRepository clientRepository;
@Autowired
public MyClientDetailService(ClientRepository clientRepository) {
this.clientRepository = clientRepository;
}
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
AuthClient client=clientRepository.findByClientId(clientId).orElseThrow(()->new BusinessException(ResultCode.USER_NOT_EXIST));
BaseClientDetails clientDetails=new BaseClientDetails();
clientDetails.setClientId(client.getClientId());
clientDetails.setClientSecret(client.getClientSecret());
clientDetails.setAuthorizedGrantTypes(Arrays.asList( client.getAuthorizedGrantTypes().split(",")));
clientDetails.setScope(Arrays.asList( client.getScope().split(",")));
return clientDetails;
}
}
package com.jty.nrsc.infrastructure.security;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
/**
* 自定义UserDetail
*
* @author Jason
* @since 2018/12/22 09:10
*/
@Data
public class MyUserDetail implements UserDetails {
private Integer userId;
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private String realName;
private boolean accountNonExpired=true;
private boolean accountNonLocked=true;
private boolean credentialsNonExpired=true;
private boolean enabled=true;
private List<String> modules;
}
package com.jty.nrsc.infrastructure.security;
import com.jty.nrsc.application.service.AuthAuthorityService;
import com.jty.nrsc.domain.model.auth.user.AuthUser;
import com.jty.nrsc.domain.model.auth.user.UserRepository;
import com.jty.nrsc.infrastructure.support.BusinessException;
import com.jty.nrsc.infrastructure.support.ResultCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class MyUserDetailService implements UserDetailsService {
private final UserRepository userRepository;
@Autowired
private AuthAuthorityService authAuthorityService;
@Autowired
public MyUserDetailService(UserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* 根据配置信息确认是否需要查找用户相应学校
* 如果需要,根据学校信息生成用户的有效期等内容
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Pattern p = Pattern.compile("^[1][0-9]{10}$"); // 验证手机号
Matcher m = p.matcher(username);
Boolean isMobile = m.matches();
AuthUser user;
MyUserDetail detail = new MyUserDetail();
if (isMobile) {
user = userRepository.findByMobile(username).orElseThrow(() -> new BusinessException(ResultCode.USER_LOGIN_ERROR));
detail.setUsername(user.getMobile());
} else {
user = userRepository.findByAccount(username).orElseThrow(() -> new BusinessException(ResultCode.USER_LOGIN_ERROR));
detail.setUsername(user.getAccount());
}
detail.setUserId(user.getId());
detail.setRealName(user.getRealName());
detail.setPassword(user.getPassword());
detail.setEnabled(user.getEnabled());
detail.setAuthorities(authAuthorityService.getAuthority(user.getId()));
return detail;
}
}
package com.jty.nrsc.infrastructure.security;
import lombok.Data;
@Data
public class RefreshTokenBean {
private String refreshToken;
}
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment