应用场景
- 用户生成文章的分享链接,可以把链接分享给其他人,其他人可以通过链接访问文章
- 用户创建分享链接时可以设置访问权限:
- 开启/关闭分享链接(关闭后链接失效)
- 是否互联网公开(公开后不校验登录)
- 是否需要访问密码(设置密码后,访问时需要输入密码)
实现逻辑
- 用户打开分享链接后,前端调用接口对链接进行校验
- 后端接口根据链接的权限设置,使用责任链模式校验,责任链的每一个
Handler
负责校验一项权限 - 后端接口返回状态码,前端根据状态码执行相应的动作(跳登录页、提示输入密码、打开文章等)
- 后端接口校验成功后返回
token
,前端携带此token
访问文章信息接口,文章信息接口再对token
二次校验是否有效,有效则返回文章信息
实现代码
- 创建状态码常量
ShareChainCode
/**
* 状态码常量
*/
public interface ShareChainCode {
/**
* 分享未开启
*/
Integer NOT_OPEN = 10001;
/**
* 请先登录
*/
Integer TO_LOGIN = 10002;
/**
* 请输入密码
*/
Integer PASSWORD_IMPORT = 10003;
/**
* 密码错误
*/
Integer PASSWORD_ERROR = 10004;
}
- 责任链参数类
ShareChainDTO
,便于以后扩展参数
import com.transnal.modules.share.entity.Share;
import lombok.Data;
import java.io.Serializable;
/**
* 责任链参数dto
*/
@Data
public class ShareChainDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分享的设置
*/
private Share share;
/**
* 访问的用户id
*/
private Long userId;
/**
* 用户输入的密码
*/
private String userKey;
}
- 创建责任链处理器抽象类
ShareHandler
,其他责任链处理器必须继承此类
import com.transnal.core.tool.api.R;
import com.transnal.modules.share.dto.ShareChainDTO;
/**
* 责任链处理器抽象类,其他责任链处理器必须继承此类
*/
public abstract class ShareHandler {
/**
* 下一个处理器
*/
protected ShareHandler nextHandler;
/**
* 设置下一个处理器
*/
public void setNextHandler(ShareHandler nextHandler) {
this.nextHandler = nextHandler;
}
/**
* 执行下一个处理器
*/
public R verify(ShareChainDTO dto) {
if (nextHandler != null) {
//执行下一个处理器
return nextHandler.execute(dto);
}
//没有下一个处理器,则说明校验通过,返回成功
return R.status(true);
}
/**
* 处理器执行内容
*/
abstract protected R execute(ShareChainDTO dto);
}
- 校验分享链接是否开启的处理器
import com.transnal.core.tool.api.R;
import com.transnal.modules.share.chain.ShareChainCode;
import com.transnal.modules.share.chain.ShareHandler;
import com.transnal.modules.share.dto.ShareChainDTO;
import com.transnal.modules.share.entity.Share;
import org.springframework.stereotype.Component;
/**
* 校验分享链接是否开启的处理器
*/
@Component
public class OpenHandler extends ShareHandler {
/**
* 第一个处理器覆写verify方法,执行自己的方法
*/
@Override
public R verify(ShareChainDTO dto) {
return this.execute(dto);
}
@Override
protected R execute(ShareChainDTO dto) {
//分享的设置
Share share = dto.getShare();
//校验分享链接是否开启
if (share == null || !share.getIsOpen()) {
return R.fail(ShareChainCode.NOT_OPEN, "分享未开启");
}
//调用父类的verify,执行下一个责任链
return super.verify(dto);
}
}
- 校验是否互联网公开的处理器
import com.transnal.core.tool.api.R;
import com.transnal.modules.share.chain.ShareChainCode;
import com.transnal.modules.share.chain.ShareHandler;
import com.transnal.modules.share.dto.ShareChainDTO;
import com.transnal.modules.share.entity.Share;
import org.springframework.stereotype.Component;
/**
* 校验是否互联网公开的处理器
*/
@Component
public class OpenInNetHandler extends ShareHandler {
@Override
protected R execute(ShareChainDTO dto) {
//分享的设置
Share share = dto.getShare();
//当前用户id
Long userId = dto.getUserId();
//如果不是互联网公开,需要校验登录状态
if (!share.getOpenInNet()) {
//校验用户是否登录
if (userId == null || userId <= 0) {
return R.fail(ShareChainCode.TO_LOGIN, "请先登录");
}
}
//执行下一个责任链
return this.verify(dto);
}
}
- 校验密码的处理器
import cn.hutool.core.util.StrUtil;
import com.transnal.core.tool.api.R;
import com.transnal.modules.share.chain.ShareChainCode;
import com.transnal.modules.share.chain.ShareHandler;
import com.transnal.modules.share.dto.ShareChainDTO;
import com.transnal.modules.share.entity.Share;
import org.springframework.stereotype.Component;
/**
* 校验密码的处理器
*/
@Component
public class PasswordHandler extends ShareHandler {
@Override
protected R execute(ShareChainDTO dto) {
//分享的设置
Share share = dto.getShare();
//用户输入的密码
String userKey = dto.getUserKey();
//校验密码
if (share.getIsKey()) {
//校验是否输入密码
if (StrUtil.isBlank(userKey)) {
return R.fail(ShareChainCode.PASSWORD_IMPORT, "请输入密码");
}
//校验密码是否正确
if (!userKey.equals(share.getKey())) {
return R.fail(ShareChainCode.PASSWORD_ERROR, "密码错误");
}
}
//执行下一个责任链
return this.verify(dto);
}
}
- 项目启动后,初始化责任链处理器执行顺序
import com.transnal.core.tool.utils.SpringUtil;
import com.transnal.modules.share.chain.handler.OpenHandler;
import com.transnal.modules.share.chain.handler.OpenInNetHandler;
import com.transnal.modules.share.chain.handler.PasswordHandler;
import lombok.Data;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* 项目启动后初始化责任链
*/
@Component
@Data
public class ShareChainPattern implements CommandLineRunner {
/**
* 第一个处理器
*/
private ShareHandler shareHandler;
@Override
public void run(String... args) {
//获取责任链处理器
OpenHandler openHandler = SpringUtil.getBean(OpenHandler.class);
OpenInNetHandler openInNetHandler = SpringUtil.getBean(OpenInNetHandler.class);
PasswordHandler keyHandler = SpringUtil.getBean(PasswordHandler.class);
//设置处理器的执行顺序
openHandler.setNextHandler(openInNetHandler);
openInNetHandler.setNextHandler(keyHandler);
//设置第一个处理器
shareHandler = openHandler;
}
}
- 接口直接调用
ShareChainPattern
即可
import cn.hutool.core.util.StrUtil;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.transnal.core.boot.ctrl.GeccoController;
import com.transnal.core.secure.utils.AuthUtil;
import com.transnal.core.tool.api.R;
import com.transnal.modules.share.chain.ShareChainPattern;
import com.transnal.modules.share.dto.ShareChainDTO;
import com.transnal.modules.share.entity.Share;
import com.transnal.modules.share.service.IShareService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 资源分享 控制器
*/
@RestController
@AllArgsConstructor
@RequestMapping("/share/v1")
public class ShareController extends GeccoController {
private IShareService shareService;
private ShareChainPattern shareChainPattern;
/**
* 验证权限
*/
@GetMapping("/verify")
@ApiOperationSupport(order = 7)
@ApiOperation(value = "验证权限", notes = "传入wikiid")
public R verify(@ApiParam(value = "分享的url") @RequestParam String url,
@ApiParam(value = "用户输入的密码") String userKey) {
//查询分享url的设置信息
Share share = shareService.findOneByUrl(url);
if (share == null) {
return R.fail("分享未开启");
}
//使用dto封装参数,方便将来扩展参数
ShareChainDTO dto = new ShareChainDTO();
//分享设置信息
dto.setShare(share);
//当前用户id
dto.setUserId(AuthUtil.getUserId());
//用户输入的密码
dto.setUserKey(StrUtil.trim(userKey));
//通过责任链校验
R verify = shareChainPattern.getShareHandler().verify(dto);
//校验未通过,返回错误信息及状态码
if (!verify.isSuccess()) {
return verify;
}
//创建加密的token,token包含创建时间、访问的文章id等信息,前端需要携带此token访问文章
String token = shareService.getToken(share);
return R.data(token);
}
}
- 其中
shareService.getToken
方法封装了加密token
代码;R
类封装了接口返回值、返回对象和状态码;代码略