1 准备工作
1.1 发送短信服务
1.1.1 接入阿里云短信服务
1.1.2 短信发送工具类
@Component
public class SmsUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(SmsUtils.class);
@Autowired
private AcSmsProperties acSmsProperties;
@Autowired
private CwosPlatformProperties cwosPlatformProperties;
/**
* 通过阿里发送短信
* @param telephone
* @param templateId
* @param templateParam
* @return
*/
public boolean sendAliSms(String telephone, String templateId, String templateParam) {
if (cwosPlatformProperties.isTestMode()) {
return true;
}
//设置超时时间-可自行调整
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//替换成你的AK
//初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile profile = DefaultProfile
.getProfile(acSmsProperties.getRegionId(),
acSmsProperties.getAccessKeyId(),
acSmsProperties.getAccessKeySecret());
try {
DefaultProfile.addEndpoint(acSmsProperties.getEndpointName(), acSmsProperties.getRegionId(),
acSmsProperties.getProduct(), acSmsProperties.getDomain());
} catch (ClientException e1) {
LOGGER.error("短信发送失败,",e1);
}
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象
SendSmsRequest request = new SendSmsRequest();
//使用post提交
request.setMethod(MethodType.POST);
//必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
request.setPhoneNumbers(telephone);
//必填:短信签名-可在短信控制台中找到
request.setSignName("云从科技");
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(templateId);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
//友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
request.setTemplateParam(templateParam);
//可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段)
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("yourOutId");
//请求失败这里会抛ClientException异常
SendSmsResponse sendSmsResponse = null;
try {
sendSmsResponse = acsClient.getAcsResponse(request);
} catch (Exception e) {
LOGGER.error("短信发送失败,",e);
}
if (sendSmsResponse != null) {
if (sendSmsResponse.getCode() != null && "OK".equals(sendSmsResponse.getCode())) {
LOGGER.info("发送短信成功,phone:{},发送内容:{}",telephone,templateParam);
return true;
} else {
LOGGER.warn("短信发送失败,code:{},message:{}", sendSmsResponse.getCode(), sendSmsResponse.getMessage());
}
}
return false;
}
}
1.2 发送邮件服务
使用javax.mail发送邮件
@Component
public class EmailUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(EmailUtils.class);
@Autowired
private CwosMailProperties cwosMailProperties;
/**
* 用户名密码验证,需要实现抽象类Authenticator的抽象方法PasswordAuthentication
*/
class MyAuthenticator extends Authenticator {
String u;
String p;
public MyAuthenticator(String u, String p) {
this.u = u;
this.p = p;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(u, p);
}
}
public Boolean sendQqExmail(String to, String title, String content, EmailSettings settings) {
Boolean flag = false;
Properties prop = new Properties();
//协议
prop.setProperty("mail.transport.protocol", settings.getProtocol());
//服务器
prop.setProperty("mail.smtp.host", settings.getHost());
//端口
prop.setProperty("mail.smtp.port", settings.getPort());
//使用smtp身份验证
prop.setProperty("mail.smtp.auth", "true");
prop.put("mail.smtp.starttls.enable", "true");
prop.put("mail.smtp.starttls.required", "true");
prop.put("mail.smtp.connectiontimeout", cwosMailProperties.getConnectionTimeout());
prop.put("mail.smtp.writetimeout", cwosMailProperties.getWriteTimeout());
prop.put("mail.smtp.ssl.trust", "*");
//
Session session = Session.getInstance(prop, new MyAuthenticator(settings.getAccount(), settings.getPassword()));
session.setDebug(true);
MimeMessage mimeMessage = new MimeMessage(session);
try {
mimeMessage.setFrom(new InternetAddress(settings.getAccount(), settings.getFrom() ));
mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
mimeMessage.setSubject(title);
mimeMessage.setSentDate(new Date());
mimeMessage.setContent(content, "text/html;charset=UTF-8");
mimeMessage.saveChanges();
Transport.send(mimeMessage);
flag = true;
} catch (MessagingException | UnsupportedEncodingException e) {
LOGGER.error("邮件发送异常", e);
}
return flag;
}
}
1.3 生成图形验证码
1.3.1 生成验证码
生成的验证码要保存到redis中,用于登录验证。
@Override
public Result<AcCaptchaOutDTO> generateCaptcha() throws ServiceException {
try {
AcCaptchaDTO acCaptchaDTO= acCaptchaProcessor.generate();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ImageIO.write(acCaptchaDTO.getImage(), "png", bout);
bout.close();
//生成对应key
String uuid= AcToolUtils.getUUID();
//将code存入redis
acCacheProvider.getAcCacheDao().set(RedisNamespaceConstant.VERIFY_CAPTCHA+ uuid, acCaptchaDTO.getCode(), acCaptchaProperties.getValidSecond());
AcCaptchaOutDTO acCaptchaOutDTO = new AcCaptchaOutDTO();
acCaptchaOutDTO.setCaptchaKey(uuid);
acCaptchaOutDTO.setImgByteArray(bout.toByteArray());
return Result.success(acCaptchaOutDTO);
} catch (Exception e) {
logger.error("生成图形验证码失败", e);
throw new ServiceException(RespCodeConstant.WRONG_GENERATE_CODE, this.getMessage(RespCodeConstant.WRONG_GENERATE_CODE));
}
}
1.3.2 将验证码的key保存到cookie
将key保存到cookie中,之后用于取redis中的验证码
@RequestMapping(value = "/captcha", method = {RequestMethod.POST, RequestMethod.GET})
public Result<AcCaptchaOutDTO> captcha(HttpServletResponse response) {
try {
Result<AcCaptchaOutDTO> captchaResult = acCaptchaService.generateCaptcha();
Cookie cookie = new Cookie("VerificationCode", captchaResult.getData().getCaptchaKey());
cookie.setMaxAge(1800);
cookie.setSecure(PlatformProperties.getHttpsEnable());
cookie.setPath("/" + rootPath + "/portal/account");
response.addCookie(cookie);
return Result.success(captchaResult.getData());
} catch (ServiceException e) {
return Result.fail(e.getCode(), e.getMessage());
} catch (Exception e) {
logger.error("获取验证码失败,原因:", e);
return Result.fail(AuthRespCodeEnum.RESPONSE_GET_CAPTCHA_FAIL.getCode(),
AuthRespCodeEnum.RESPONSE_GET_CAPTCHA_FAIL.getMessage());
}
}
2 登录
登录验证用户名和密码,生成的token要保存到redis中,用于网关验证
// 校验成功,取token
AcClaimsDTO acClaimsDTO = new AcClaimsDTO(acLoginDTO.getId(), acLoginDTO.getLoginName(),
acLoginDTO.getTelephone(), acLoginDTO.getEmail(), acLoginDTO.getBusinessId(), loginParam.getSource());
String tokenKey = String.format(RedisNamespaceConstant.LOGIN_TOKEN_KEY, loginParam.getSource(), acLoginDTO.getId());
String tokenJson=tokenProvider.generate(tokenKey, JSON.toJSONString(acClaimsDTO),loginParam.getAccessTokenExp(),loginParam.getRefreshTokenExp());
AcTokenDTO acTokenDTO = JSON.parseObject(tokenJson, AcTokenDTO.class);
//多端登录处理类型默认为2,不支持多端登陆
String lastToken = acCacheProvider.getAcCacheDao().get(RedisNamespaceConstant.MULTI_LOGIN_ID +acLoginDTO.getId() + ":" + acClaimsDTO.getSource());
tokenProvider.clear(lastToken);
acCacheProvider.getAcCacheDao().set(RedisNamespaceConstant.MULTI_LOGIN_ID+acLoginDTO.getId() + ":" + acClaimsDTO.getSource(), acTokenDTO.getAccessToken());
acLoginDTO.setAccessToken(acTokenDTO.getAccessToken());
acLoginDTO.setAccessTokenExp(acTokenDTO.getAccessTokenExp());
acLoginDTO.setRefreshToken(acTokenDTO.getRefreshToken());
acLoginDTO.setRefreshTokenExp(acTokenDTO.getRefreshTokenExp());
if (null != acAccount.getUserResult()) {
UserResult ldapUserResult = new UserResult();
acLoginDTO.setUserResult(BeanCopyUtils.copyProperties(acAccount.getUserResult(), ldapUserResult));
}
return Result.success(acLoginDTO);
3 网关认证
从redis中读取token,和用户提交的token进行比较
private Object loginValidate() {
RequestContext ctx = RequestContext.getCurrentContext();
// 先判断路径,如果是登录不需要校验
String url = ctx.getRequest().getRequestURI();
Boolean flag = Pattern.matches(".*login|.*token", url);
logger.info("是否为登录:"+flag);
if (flag) {
cookie.setHttpOnly(true);
cookie.setSecure(true);
ctx.getResponse().addCookie(cookie);
return null;
}
String token = this.getToken();
logger.info("请求中的token为:"+token);
if (StringUtils.isNotBlank(token)) {
String passed = redisTemplate.opsForValue().get("JWT_TOKEN_$" + token);
logger.info("redis存在性验证:"+!(passed == null || passed == ""));
if (passed == null || passed == "") {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody(JSONObject.toJSON(CloudwalkResult.fail("56000066", "Token已失效,请重新登录!")).toString());
ctx.getResponse().setContentType("application/json;charset=UTF-8");
return null;
}
try {
this.validateToken(token);
logger.info("token校验正常:"+token);
//校验成功后刷新TOKEN过期时间
Claims c = getClaims(token);
final JSONObject jo = JSONObject.parseObject(JSONObject.toJSONString(c));
JWTRedisDaoImpl dao = new JWTRedisDaoImpl(redisTemplate);
redisTemplate.expire("JWT_TOKEN_$" + token, tokenTime*1000, TimeUnit.MILLISECONDS);
}catch(Exception e) {
logger.info("token校验异常:"+e+":"+token);
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody(JSONObject.toJSON(CloudwalkResult.fail("56000065", "Token已失效,请重新登录!")).toString());
ctx.getResponse().setContentType("application/json;charset=UTF-8");
return null ;
}
return null ;
}
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody(JSONObject.toJSON(CloudwalkResult.fail("56000065", "Token已失效,请重新登录!")).toString());
ctx.getResponse().setContentType("application/json;charset=UTF-8");
return null;
}
4 权限管理
用户<–>角色<–>权限
4.1 用户管理
添加用户的时候,需要添加账号(登录的时候用),同时要配置用户角色。
4.1.1 用户批量导入
(1)设置模板
/**
* 更新模板配置
*
* @throws ServiceException
*/
@Override
@ParamsValidate(argsIndexs = {0})
public Result<Boolean> update(BatchConfigParam param, CallContext context)
throws ServiceException {
BatchConfig batchConfig = new BatchConfig();
BeanUtils.copyProperties(param, batchConfig);
batchConfig.setBusinessId(context.getCompany().getCompanyId());
batchConfig.setCreateTime(System.currentTimeMillis());
batchConfig.setCreateUserId(context.getUser().getCaller());
BatchConfig batchConfigExist = batchConfigMapper.selectByBusinessId(context.getCompany().getCompanyId());
if (batchConfigExist == null) {
batchConfig.setId(ToolUtil.generateUUID());
int count = batchConfigMapper.insert(batchConfig);
if (count < 1) {
return CloudwalkResult.fail(BatchRespCodeConstant.BATCHCONFIG_INSERT_ERROR,
this.getMessage(BatchRespCodeConstant.BATCHCONFIG_INSERT_ERROR));
}
} else {
int count = batchConfigMapper.update(batchConfig);
if (count < 1) {
return CloudwalkResult.fail(BatchRespCodeConstant.BATCHCONFIG_INSERT_ERROR,
this.getMessage(BatchRespCodeConstant.BATCHCONFIG_INSERT_ERROR));
}
}
return Result.success(true);
}
(2)下载模板
导出excel的时候,需要输入header 数据map
/**
* <p>Title: createTemplate</p>
* <p>Description: 创建导出模板</p>
*
* @param batchConfig
* @param context
* @throws IOException
* @throws ServiceException
* @throws ResourceServiceException
*/
public String createTemplate(BatchConfig batchConfig, CloudwalkCallContext context) throws IOException {
//获取模板类型、处理表头数据
List<Map<String, String>> headers = getHeaders(batchConfig.getConfigJson());
//未获取表头 则用户未配置模板
if (CollectionUtils.isEmpty(headers)) {
return "";
}
//生成zip包的临时文件夹
String folderName = ToolUtil.generateUUID();
//获取生成模板存放的基础路径
String tempFilePath = cwosDownloadProperties.getTemplatePath() + folderName;
File tempFile = new File(tempFilePath);
if (!tempFile.exists()) {
tempFile.mkdirs();
}
//初始化默认数据
Map<String, String> dataMap = initdata();
//获取项目中模板所在位置的真实路径 /upload/template/
String tempRealPath = cwosDownloadProperties.getTemplatePath();
String imageName = getImg(tempRealPath);
// 文件名
if (TemplateType.from(batchConfig.getType()) == TemplateType.FILE_NAME) {
//图片文件名
StringBuilder picName = new StringBuilder();
//根据获取的表头数据,去dataMap中拿到默认数据
for (int i = 0; i < headers.size(); i++) {
Map<String, String> map = headers.get(i);
//根据表头拼接图片文件名称
if (i == (headers.size() - 1)) {
picName.append(dataMap.get(map.get("id")));
} else {
picName.append(dataMap.get(map.get("id")) + "-");
}
}
//将原始样例图片 复制到 即将生成zip包的临时文件夹
FileUtils.copyFile(new File(tempRealPath + imageName), new File(tempFile.getAbsoluteFile() + File.separator + picName + ".jpg"));
} else {
//原始样例图片 copy 到 zip包的临时文件夹
FileUtils.copyFileToDirectory(new File(tempRealPath + imageName), tempFile);
//根据模板类型生成相应的模板
File filename = null;
if (TemplateType.from(batchConfig.getType()) == TemplateType.CSV) {
filename = new File(tempFile, "人员批量导入模板.csv");
exportCsv(filename.getAbsolutePath(), headers, dataMap);
} else if (TemplateType.from(batchConfig.getType()) == TemplateType.EXCEL) {
filename = new File(tempFile, "人员批量导入模板.xls");
exportExcel(filename.getAbsolutePath(), headers, dataMap);
}
}
//将服务器上存放Excel/Csv/图片的文件夹打成zip包
File zipFile = new File(tempRealPath, folderName + ".zip");
ZipUtil.zipMultiFile(tempFile.getAbsolutePath(), zipFile.getAbsolutePath(), false,
batchImportService.getPersonBatchSetting(PersonBatchConstant.FILE_ENCODE, context));
return tempFilePath;
}
(3)excel导入
@Override
public CloudwalkResult<DictBatchImportResult> batchImport(DictImportParam dictImportParam, CloudwalkCallContext cloudwalkCallContext){
//校验文件是否存在
CloudwalkResult<byte[]> fileBytes = fileService.get(dictImportParam.getFilePath());
if (fileBytes.getData() == null){
return CloudwalkResult.fail(BatchRespCodeConstant.BATCHIMPORT_FILE_NULL, this.getMessage(BatchRespCodeConstant.BATCHIMPORT_FILE_NULL));
}
List<List<String>> result = new ArrayList<>();
int skipRowIndex = 1;
ExcelUtils.readFromExcel(result, new ByteArrayInputStream(fileBytes.getData()), 0, skipRowIndex, 0, false);
//校验是否有数据
if (result.size() < skipRowIndex + 1){
return CloudwalkResult.fail(BatchRespCodeConstant.BATCHIMPORT_DATA_EMPTY, this.getMessage(BatchRespCodeConstant.BATCHIMPORT_DATA_EMPTY));
}
//校验文件格式
List<String> titles = result.get(0);
String[] titleDicts = DictConstant.titleDicts();
for (int i = 0; i<titleDicts.length ; i++){
if (titles.size() > i){
if (!titleDicts[i].equals(titles.get(i).trim())) {
return CloudwalkResult.fail(BatchRespCodeConstant.BATCHIMPORT_FILE_FORMAT_ERROR,
this.getMessage(BatchRespCodeConstant.BATCHIMPORT_FILE_FORMAT_ERROR));
}
} else {
return CloudwalkResult.fail(BatchRespCodeConstant.BATCHIMPORT_FILE_FORMAT_ERROR,
this.getMessage(BatchRespCodeConstant.BATCHIMPORT_FILE_FORMAT_ERROR));
}
}
//第一行信息,为标题信息,typeCode,name,code,status,remark
result = result.subList(1, result.size());
List<DictAddParam> addParamList = new ArrayList<>();
for (List<String> line : result) {
DictAddParam addParam = getDictAddParam(line);
addParamList.add(addParam);
}
CloudwalkResult<DictBatchImportResult> resultCloudwalkResult;
try {
resultCloudwalkResult = dictService.batchImport(addParamList, cloudwalkCallContext, skipRowIndex);
} catch (ServiceException e) {
return CloudwalkResult.fail(e.getCode(), getMessage(e.getCode()));
}
DictBatchImportResult dictBatchImportResult = resultCloudwalkResult.getData();
if (dictBatchImportResult.getFailCount() > 0 ){
byte [] files = writeFailedDict2Stream(dictBatchImportResult.getResultList());
//写入到文件
fileService.save(DICT_FILE_INSERT_FAILED_PATH + dictBatchImportResult.getBatchId() + xlsx, files, false);
dictBatchImportResult.setResultList(null);
//全部导入失败
if (dictBatchImportResult.getSuccessCount() ==0){
CloudwalkResult<DictBatchImportResult> cloudwalkResult = CloudwalkResult.success(dictBatchImportResult);
cloudwalkResult.setCode(BatchRespCodeConstant.BATCHIMPORT_ALL_ERROR);
cloudwalkResult.setMessage( getMessage(BatchRespCodeConstant.BATCHIMPORT_ALL_ERROR));
return cloudwalkResult;
}
//部分导入失败
CloudwalkResult<DictBatchImportResult> cloudwalkResult = CloudwalkResult.success(dictBatchImportResult);
cloudwalkResult.setCode(BatchRespCodeConstant.BATCHIMPORT_PART_ERROR);
cloudwalkResult.setMessage( getMessage(BatchRespCodeConstant.BATCHIMPORT_PART_ERROR));
return cloudwalkResult;
}
//全部导入成功
return resultCloudwalkResult;
}
4.2 角色管理
4.3 权限管理
一个应用有多个接口,若干接口构成一个资源。分配这些资源就是分配权限。