裕谷

裕谷 查看完整档案

深圳编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

_
| |__ __ _
| '_ | | | |/ _` |
| |_) | |_| | (_| |
|_.__/ \__,_|\__, |

         |___/ bottleneck

个人动态

裕谷 回答了问题 · 11月22日

解决负数时间戳日期转换问题

时区问题,谷歌浏览器时间是1901年前的时区按+805而不是+800,因此时间戳-2641363543对应 1886-04-20 00:00:00 的时区是+805。
image

关注 3 回答 2

裕谷 发布了文章 · 7月22日

Spring Cloud OAuth2 实现Bad client credentials异常自定义信息返回

开始

默认情况下申请令牌访问oauth/token未携带client_secret参数时会返回Bad client credentials
屏幕快照 2020-07-22 上午1.40.34.png
如果直接通过AuthenticationEntryPoint是无法自定义返回的信息,我们需要重写过滤器ClientCredentialsTokenEndpointFilter

自定义过滤器

public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {

    private AuthorizationServerSecurityConfigurer configurer;
    private AuthenticationEntryPoint authenticationEntryPoint;

    public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) {
        this.configurer = configurer;
    }

    @Override
    public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
        // 把父类的干掉
        super.setAuthenticationEntryPoint(null);
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @Override
    protected AuthenticationManager getAuthenticationManager() {
        return configurer.and().getSharedObject(AuthenticationManager.class);
    }

    @Override
    public void afterPropertiesSet() {
        setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                authenticationEntryPoint.commence(httpServletRequest, httpServletResponse, e);
            }
        });
        setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                // 无操作-仅允许过滤器链继续到令牌端点
            }
        });
    }

}

自定义AuthenticationEntryPoint

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        e.printStackTrace();
        response.setStatus(200);
        Result result = Result.buildFail(e.getMessage());
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.getWriter().print(objectMapper.writeValueAsString(result));
        response.getWriter().flush();
    }

}

修改授权服务器配置

public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
        security.addTokenEndpointAuthenticationFilter(endpointFilter);
        // 注意:security不需要在调用allowFormAuthenticationForClients方法
        security.authenticationEntryPoint(authenticationEntryPoint)
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }
    
    // 此处省略其他代码...

}

效果

111.png

项目源码

https://gitee.com/yugu/demo-oauth2

查看原文

赞 1 收藏 1 评论 0

裕谷 收藏了文章 · 5月25日

IntelliJ IDEA 2020最新注册码(亲测有效,可激活至 2089 年,持续更新~)

idea 2020版本最新注册码

申明:本教程 IntelliJ IDEA 破解补丁、激活码均收集于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除。

注意

  • 本教程适用于 IntelliJ IDEA 2020.1 以下所有版本,请放心食用~
  • 本教程适用于 JetBrains 全系列产品,包括 Pycharm、IDEA、WebStorm、Phpstorm、Datagrip、RubyMine、CLion、AppCode 等。
  • 本教程适用 Windows/Mac/Linux 系统,文中以 Windows 系统为例做讲解。

一、前言

IDEA 目前已经更新到最新的 2020 版本了,4月10日那天,群里的小伙伴私聊问我,最新的 2020 版本要如何激活呢?

自己在网上搜罗了各种注册码尝试激活,均以失败告终,有的虽然当时成功了,当时很快就失效了,也许是 IDEA 最近查杀的比较严吧~ 太难了~

但是,最终还是让我找到了破解方法,这里要感谢z大提供的破解补丁。无图无真相,这里展示一下个人破解至 2089 年的效果图:

如何破解呢?下文是个人 IntelliJ IDEA 2020版本的激活破解全过程,步骤非常详细哟~

二、下载最新的 IDEA 2020 版本安装包

我们选择从 IDEA 官网下载:https://www.jetbrains.com/idea/download/

PS: 网络不好的小伙伴,我在网盘里也存了一份 IDEA 2020 版本的安装包,大家可以自行下载。

点击下载,静心等待其下载完毕即可。

三、开始破解

  1. 下载完成后,双击 ideaIU-2020.1.exe,打开安装软件;
PS: 如果电脑上之前有安装老版本的 IDEA, 会提示卸载,小伙伴们按照提示,先卸载掉老版本的 IDEA。
  1. 安装目录默认为 C:\Program Files\JetBrains\IntelliJ IDEA 2020.1, 这里笔者选择的是默认路径;

  1. 勾选自己想要创建的桌面快捷方式,笔者的操作系统是 64 位的,所以勾选的 64 位快捷方式:

  1. 点击 next, 安心等待其安装完成:

  1. 安装完成后,勾选 Run IntelliJ IDEA,点击 finish 运行软件:

  1. 会先弹出一个注册框,勾选 Evaluate for free, 点击 Evaluate:

  1. 将网盘中 2020 版本文件夹中的破解补丁 jetbrains-agent.jar拖入 IDEA 界面中:

页面提取人数太多,导致破解补丁容易被封,一直更换非常麻烦,为限制人数,目前暂不提供页面直接提取,改为从笔者公众号提取。

需要的小伙伴,请关注微信公众号: Java学习者社区, 或者扫描下方公众号二维码,回复关键字:idea, 即可免费无套路获取激活码、破解补丁,持续更新中~。

  1. 拖入补丁后会弹框,点击 restart 重启 idea:

  1. 配置助手会提示您,需要使用哪种激活方式,这里我们选择默认的 Activation Code,通过注册码来激活,点击为IDEA安装

  1. 点击,重启IDEA, 即激活成功啦~

三、验证是否激活成功

你说激活成功就激活成功了?我咋不信呢?

别急,接下来我们就来验证一下 IDEA 是否已经激活成功了,步骤如下:

  1. 进入 IDEA 界面后,点击 Help -> Register 查看:

  1. 可以看到,已经成功激活至 2089 年辣,撸到老,哈哈~

页面提取人数太多,导致破解补丁容易被封,一直更换非常麻烦,为限制人数,目前暂不提供页面直接提取,改为从笔者公众号提取。

需要的小伙伴,请关注微信公众号: Java学习者社区, 或者扫描下方公众号二维码,回复关键字:idea, 即可免费无套路获取激活码、破解补丁,持续更新中~。

四、声明

本教程只做个人学习使用,请勿用于商业用途!

查看原文

裕谷 发布了文章 · 5月18日

Android 侧边滑动关闭Activity【废弃】

0.效果图

11.gif

1.设置Activity样式属性

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowIsTranslucent">true</item>
</style>

2.自定义侧边阴影视图

class SlideBackView extends View {

        private Paint mBgPaint, mShadowPaint;
        private RectF mBgRectF, mShadowRectF;
        private float mRatio;
        private float mShadowSize;

        public SlideBackView(Context context) {
            super(context);
            mBgPaint = new Paint();
            mBgPaint.setAntiAlias(true);
            mBgPaint.setColor(0xff000000);
            mShadowPaint = new Paint();
            mShadowPaint.setAntiAlias(true);
            mShadowPaint.setStyle(Paint.Style.FILL);
            mShadowSize = dp2px(15);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mBgRectF = new RectF();
            mBgRectF.top = 0;
            mBgRectF.left = 0;
            mBgRectF.bottom = MeasureSpec.getSize(heightMeasureSpec);

            mShadowRectF = new RectF();
            mShadowRectF.top = 0;
            mShadowRectF.bottom = MeasureSpec.getSize(heightMeasureSpec);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int width = getMeasuredWidth();
            float right = mRatio * width;
            mBgRectF.right = right;
            mBgPaint.setAlpha((int) (128 * (1 - mRatio)));
            canvas.drawRect(mBgRectF, mBgPaint);
            mShadowRectF.left = right - mShadowSize;
            mShadowRectF.right = right;
            mShadowPaint.setShader(new LinearGradient(mShadowRectF.left, 0, mShadowRectF.right, 0, 0x00000000, 0x26000000, Shader.TileMode.CLAMP));
            canvas.drawRect(mShadowRectF, mShadowPaint);
        }

        public void setDistance(float ratio) {
            mRatio = ratio;
            invalidate();
        }

        private float dp2px(float dpValue) {
            float density = getResources().getDisplayMetrics().density;
            return dpValue * density + 0.5F;
        }
    }

3.定义可滑动的Activity父类

public class SlideBaseActivity extends AppCompatActivity implements ValueAnimator.AnimatorUpdateListener {

    private boolean isAnimate, isSlide, isHandle;
    private float moveNum;
    private float lastX, lastY;
    private int lastPointerCount;
    private float mAnimatedValue;
    private ValueAnimator mValueAnimator;
    private SlideBackView mSlideBackView;
    private float mTouchSlop;
    private List<ShieldView> shieldViews = new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        super.onCreate(savedInstanceState);
        initAnimator();
        initSlideBackView();
        mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mAnimatedValue = (float) animation.getAnimatedValue();
        moveView(mAnimatedValue);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (!isAnimate) {
            float x = event.getRawX();
            float y = event.getRawY();
            if (event.getPointerCount() != lastPointerCount) {
                lastPointerCount = event.getPointerCount();
                lastX = x;
                lastY = y;
            }
            float offsetX, offsetY;
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    offsetX = x - lastX;
                    offsetY = y - lastY;
                    if (!isHandle) {
                        float absX = Math.abs(offsetX);
                        float absY = Math.abs(offsetY);
                        if (absX > mTouchSlop) {
                            if (absX * 0.5f > absY) {
                                isSlide = true;
                                checkSlide((int) x, (int) y);
                            } else {
                                isSlide = false;
                            }
                            isHandle = true;
                        }
                    } else if (isSlide) {
                        moveNum += offsetX;
                        if (moveNum < 0) {
                            moveNum = 0;
                        }
                        moveView(moveNum);
                        lastX = event.getX();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (isHandle) {
                        isSlide = false;
                        isHandle = false;
                        isAnimate = true;
                        int width = getWindow().getDecorView().getMeasuredWidth();
                        if (moveNum < width / 3f) {
                            mValueAnimator.setFloatValues(moveNum, 0);
                        } else {
                            mValueAnimator.setFloatValues(moveNum, width);
                        }
                        mValueAnimator.start();
                        moveNum = 0;
                        lastPointerCount = 0;
                    }
            }
        }
        return isSlide || super.dispatchTouchEvent(event);
    }

    /**
     * 添加禁用滑动的子布局
     */
    public void addShieldView(View view) {
        shieldViews.add(new ShieldView(false, view));
    }

    /**
     * 添加水平禁用滑动的子布局
     */
    public void addHorizontalShieldView(View view) {
        shieldViews.add(new ShieldView(true, view));
    }

    /**
     * 移除禁用滑动的子布局
     */
    public void removeShieldView(View view) {
        for (ShieldView v : shieldViews) {
            if (v.view != null && v.view.equals(view)) {
                shieldViews.remove(v);
                break;
            }
        }
    }

    /**
     * 清空禁用滑动的子布局
     */
    public void clearShieldView() {
        shieldViews.clear();
    }

    private void initAnimator() {
        mValueAnimator = new ValueAnimator();
        mValueAnimator.setDuration(300);
        mValueAnimator.addUpdateListener(this);
        mValueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimate = false;
                if (mAnimatedValue == getWindow().getDecorView().getMeasuredWidth()) {
                    SlideBaseActivity.super.finish();
                    overridePendingTransition(0, 0);
                }
            }
        });
    }

    private void initSlideBackView() {
        mSlideBackView = new SlideBackView(this);
        ViewGroup decorView = (ViewGroup) getWindow().getDecorView();
        decorView.addView(mSlideBackView);
    }

    private void moveView(float moveX) {
        ViewGroup decorView = (ViewGroup) getWindow().getDecorView();
        mSlideBackView.setDistance(moveX / decorView.getMeasuredWidth());
        int count = decorView.getChildCount();
        for (int i = 0; i < count; i++) {
            View view = decorView.getChildAt(i);
            if (view != mSlideBackView) {
                view.setX(moveX);
            }
        }
    }

    private void checkSlide(int x, int y) {
        for (ShieldView v : shieldViews) {
            Rect rect = new Rect();
            v.view.getGlobalVisibleRect(rect);
            if (rect.contains(x, y) && (!(lastX < x && !v.view.canScrollHorizontally(-1)) || (!v.isHorizontal))) {
                isSlide = false;
            }
        }
    }
    
    class ShieldView {
        boolean isHorizontal;
        View view;

        public ShieldView(boolean isHorizontal, View view) {
            this.isHorizontal = isHorizontal;
            this.view = view;
        }
    }
}

4.使用

继承SlideBaseActivity类,可调用addShieldViewaddHorizontalShieldView方法解决事件冲突。

5.项目源码

https://gitee.com/yugu/slide-demo

查看原文

赞 0 收藏 0 评论 0

裕谷 发布了文章 · 4月27日

ijkplayer

俺编译后的so:https://lanzous.com/ic0bquj

项目地址:https://github.com/Bilibili/i...
ndk-r10e:https://dl.google.com/android...
编译x86_64的so时才需要安装yasm

git clone https://github.com/Bilibili/ijkplayer

cd ijkplayer
./init-android.sh
./init-android-openssl.sh

cd android/contrib
./compile-openssl.sh clean
./compile-openssl.sh all
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all

cd ..
./compile-ijk.sh all

查看原文

赞 0 收藏 0 评论 0

裕谷 赞了文章 · 3月3日

控制弹窗展示顺序

工作中遇到一个需求,就是控制用户进入应用时自动打开弹窗的展示顺序。

需求和现状

用户进入应用时会展示一系列的弹窗,这些弹窗的展示内容以及展示与否取决于进入应用时的一系列请求结果。由于接口请求返回数据的时间不确定,所以依赖不同接口的弹窗之间的展示顺序也不确定,且多个弹窗会出现叠加展示的问题,用户体验很不好。

产品需求就是希望弹窗的展示顺序可控,并且同时只展示一个弹窗。

解决方案

解决思路是这样:

封装一个中间层,所有的弹窗不直接展示出来,而是把展示的处理函数统一放在中间层的数据中,等待所有依赖接口都请求完成,也就是弹窗数据都加入到中间层之后,再按照既定顺序依次执行弹窗的展示函数。

依照这个思路,我封装了一个 PopControl 的类,针对上面几点分别暴露了下面几个 api:

- popControl.reset(),用来重置依赖接口状态和弹窗数据
- popControl.load(key),用来标记该接口已经请求完成
- popControl.push(key, callback, startDelay = 100, endDelay = 100, repeatPushShow = true),用来把弹窗插入到弹窗队列中
- popControl.next(key),用来标记该弹窗已展示完成

项目 github 地址:前往

使用

具体使用的时候,需要先实例化出来一个 popControl,构造函数接收两个参数,第一个参数所有弹窗的 key 组成的数组 popKeys(按照展示顺序排序),第二个参数是所有依赖接口的 key 组成的数组。

然后在接口请求成功之后 load 对应的 key,把依赖该接口的弹窗展示函数 push 进 popControl 中,直到所有的接口都 load 完成,popControl 就会按照 popKeys 的顺序依次展示弹窗。每个弹窗展示完成之后要调用 popControl.next(key),标记当前弹窗已经展示完成,方便展示下一个弹窗。

示例代码如下:

import PopControl from "popControl"

const popKeys = ["pop1", "pop2", "pop3"];
const interfaces = ["ajaxSignin", "ajaxUseInfo"];
const popControl = new PopControl(popKeys, interfaces);

request("/ajaxSignin").then(() => {
  // 把依赖这个接口的弹窗展示函数 push 进弹窗队列
  popControl.push("pop1", () => {
    // 展示 pop1 的操作
    this.showPop1 = true;
  })
  popControl.push("pop2", () => {
    // 展示 pop2 的操作
    this.showPop2 = true;
  })
  popControl.load("ajaxSignin);
})

request("/ajaxUseInfo").then(() => {
  // 把依赖这个接口的弹窗展示函数 push 进弹窗队列
  popControl.push("pop3", () => {
    // 展示 pop3 的操作
    this.showPop3 = true;
  })
  popControl.load("ajaxUseInfo);
})

function pop1Close() {
  this.showPop1 = false;
  // 标记 pop1 已经展示完成,可以展示下一个弹窗
  popControl.next("pop1");
}

function pop2Close() {
  this.showPop2 = false;
  // 标记 pop2 已经展示完成,可以展示下一个弹窗
  popControl.next("pop2");
}

function pop3Close() {
  this.showPop3 = false;
  // 标记 pop3 已经展示完成,可以展示下一个弹窗
  popControl.next("pop3");
}
查看原文

赞 2 收藏 2 评论 3

裕谷 发布了文章 · 2019-09-07

Spring Cloud OAuth2 实现自定义返回格式

实现效果

图片描述

启用授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    //......
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      
        endpoints.tokenEnhancer(new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
            //在此追加返回的数据
                DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
                CustomUser user = (CustomUser) oAuth2Authentication.Principal();
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("nickname", user.getNickname());
                map.put("mobile", user.getMobile());
                map.put("avatar",user.getAvatar());
                token.setAdditionalInformation(map);
                return oAuth2AccessToken;
            }
        });
    }
    
}

创建响应实体

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result {

    //是否成功
    private boolean success;

    //返回码
    private int code;

    //返回信息
    private String msg;

    //返回数据
    private Object data;

    public static Result build(Object data) {
        return new Result(true, 200, "操作成功",data);
    }
    
}

重写令牌申请接口

@RestController
@RequestMapping("/oauth")
public class OauthController {

    @Autowired
    private TokenEndpoint tokenEndpoint;

    @GetMapping("/token")
    public Result getAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        return custom(tokenEndpoint.getAccessToken(principal, parameters).getBody());
    }

    @PostMapping("/token")
    public Result postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        return custom(tokenEndpoint.postAccessToken(principal, parameters).getBody());
    }

    //自定义返回格式
    private Result custom(OAuth2AccessToken accessToken) {
        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
        Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation());
        data.put("accessToken", token.getValue());
        if (token.getRefreshToken() != null) {
            data.put("refreshToken", token.getRefreshToken().getValue());
        }
        return Result.build(data);
    }

}

项目源码

https://gitee.com/yugu/demo-o...

查看原文

赞 3 收藏 3 评论 0

裕谷 发布了文章 · 2019-07-27

Spring Cloud OAuth2 自定义授权类型实现其他方式登录(短信验证码登录、手机号密码登录)

实现效果

使用手机号密码进行授权:
图片描述
使用手机号短信验证码进行授权:
图片描述
通过访问令牌获取当前用户细节:
图片描述

添加依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

创建用户细节服务

@Service
public class CustomUserDetailsService {

    public UserDetails loadUserByPhoneAndPassword(String phone, String password) {
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(password)) {
            throw new InvalidGrantException("无效的手机号或短信验证码");
        }
        // 判断成功后返回用户细节
        return new User(phone, "", AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user,root"));
    }

    public UserDetails loadUserByPhoneAndSmsCode(String phone, String smsCode) {
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(smsCode)) {
            throw new InvalidGrantException("无效的手机号或短信验证码");
        }
        // 判断成功后返回用户细节
        return new User(phone, "", AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user,root"));
    }

}

创建自定义抽象令牌授予者

public abstract class AbstractCustomTokenGranter extends AbstractTokenGranter {

    private final OAuth2RequestFactory requestFactory;

    protected AbstractCustomTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.requestFactory = requestFactory;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = tokenRequest.getRequestParameters();
        CustomUser customUser = getCustomUser(parameters);
        if (customUser == null) {
            throw new InvalidGrantException("无法获取用户信息");
        }
        OAuth2Request storedOAuth2Request = this.requestFactory.createOAuth2Request(client, tokenRequest);
        PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(customUser, null, customUser.getAuthorities());
        authentication.setDetails(customUser);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(storedOAuth2Request, authentication);
        return oAuth2Authentication;
    }

    protected abstract UserDetails getUserDetails(Map<String, String> parameters);
}

手机号密码登录令牌授予者

public class PhonePasswordCustomTokenGranter extends AbstractCustomTokenGranter {

    private CustomUserDetailsService userDetailsService;

    public PhonePasswordCustomTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, CustomUserDetailsService userDetailsService) {
        super(tokenServices, clientDetailsService, requestFactory,"custom_phone_pwd");
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected UserDetails getUserDetails(Map<String, String> parameters) {
        String phone = parameters.get("phone");
        String password = parameters.get("password");
        return userDetailsService.loadUserByPhoneAndPassword(phone, password);
    }
}

短信验证码登录令牌授予者

public class PhoneSmsCustomTokenGranter extends AbstractCustomTokenGranter {

    private CustomUserDetailsService userDetailsService;

    public PhoneSmsCustomTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, CustomUserDetailsService userDetailsService) {
        super(tokenServices, clientDetailsService, requestFactory,"custom_phone_sms");
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected UserDetails getUserDetails(Map<String, String> parameters) {
        String phone = parameters.get("phone");
        String smsCode = parameters.get("sms_code");
        return userDetailsService.loadUserByPhoneAndSmsCode(phone, smsCode);
    }
}

启用授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public CustomUserDetailsService customUserDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //定义一个客户端支持自定义的授权类型
        clients.inMemory()
                .withClient("demo")
                .secret(passwordEncoder().encode("demo"))
                .authorizedGrantTypes("custom_phone_pwd","custom_phone_sms")
                .scopes("all");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
        
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
        endpoints.tokenEnhancer(new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
                DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
                CustomUser user = (CustomUser) oAuth2Authentication.getPrincipal();
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("nickname", user.getNickname());
                map.put("mobile", user.getMobile());
                map.put("avatar",user.getAvatar());
                token.setAdditionalInformation(map);
                return oAuth2AccessToken;
            }
        });

    }

    private List<TokenGranter> getTokenGranters(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        return new ArrayList<>(Arrays.asList(
                new PhoneSmsCustomTokenGranter(tokenServices, clientDetailsService, requestFactory, customUserDetailsService),
                new PhonePasswordCustomTokenGranter(tokenServices, clientDetailsService, requestFactory, customUserDetailsService)
        ));
    }
}

开启资源认证

@Configuration
@EnableResourceServer
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }
}

实现获取用户细节接口

@RestController
public class AuthController {
    @RequestMapping("/current-info")
    public Object getUser(Authentication authentication) {
        return authentication;
    }
}

项目源码

https://gitee.com/yugu/demo-o...

总结

通过继承AbstractCustomTokenGranter抽象令牌授予者类实现getUserDetails方法获取需要的参数并调用CustomUserDetailsService用户细节服务类的方法认证获取用户细节数据。

查看原文

赞 10 收藏 5 评论 13

裕谷 赞了文章 · 2019-06-08

如何在 JS 循环中正确使用 async 与 await

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。



为了保证的可读性,本文采用意译而非直译。

asyncawait 的使用方式相对简单。 蛤当你尝试在循环中使用await时,事情就会变得复杂一些。

在本文中,分享一些在如果循环中使用await值得注意的问题。

准备一个例子

对于这篇文章,假设你想从水果篮中获取水果的数量。

const fruitBasket = {
 apple: 27,
 grape: 0,
 pear: 14
};

你想从fruitBasket获得每个水果的数量。 要获取水果的数量,可以使用getNumFruit函数。

const getNumFruit = fruit => {
  return fruitBasket[fruit];
};

const numApples = getNumFruit('apple');
console.log(numApples); //27

现在,假设fruitBasket是从服务器上获取,这里我们使用 setTimeout 来模拟。

const sleep = ms => {
  return new Promise(resolve => setTimeout(resolve, ms))
};

const getNumFruie = fruit => {
  return sleep(1000).then(v => fruitBasket[fruit]);
};

getNumFruit("apple").then(num => console.log(num)); // 27

最后,假设你想使用awaitgetNumFruit来获取异步函数中每个水果的数量。

const control = async _ => {
  console.log('Start')

  const numApples = await getNumFruit('apple');
  console.log(numApples);

  const numGrapes = await getNumFruit('grape');
  console.log(numGrapes);

  const numPears = await getNumFruit('pear');
  console.log(numPears);

  console.log('End')
}

图片描述

在 for 循环中使用 await

首先定义一个存放水果的数组:

const fruitsToGet = [“apple”, “grape”, “pear”];

循环遍历这个数组:

const forLoop = async _ => {
  console.log('Start');
  
  for (let index = 0; index < fruitsToGet.length; index++) {
    // 得到每个水果的数量
  }

  console.log('End')
}

for循环中,过上使用getNumFruit来获取每个水果的数量,并将数量打印到控制台。

由于getNumFruit返回一个promise,我们使用 await 来等待结果的返回并打印它。

const forLoop = async _ => {
  console.log('start');

  for (let index = 0; index < fruitsToGet.length; index ++) {
    const fruit = fruitsToGet[index];
    const numFruit = await getNumFruit(fruit);
    console.log(numFruit);
  }
  console.log('End')
}

当使用await时,希望JavaScript暂停执行,直到等待 promise 返回处理结果。这意味着for循环中的await 应该按顺序执行。

结果正如你所预料的那样。

“Start”;
“Apple: 27”;
“Grape: 0”;
“Pear: 14”;
“End”;

图片描述

这种行为适用于大多数循环(比如whilefor-of循环)…

但是它不能处理需要回调的循环,如forEachmapfilterreduce。在接下来的几节中,我们将研究await 如何影响forEach、map和filter

在 forEach 循环中使用 await

首先,使用 forEach 对数组进行遍历。

const forEach = _ => {
  console.log('start');

  fruitsToGet.forEach(fruit => {
    //...
  })

  console.log('End')
}

接下来,我们将尝试使用getNumFruit获取水果数量。 (注意回调函数中的async关键字。我们需要这个async关键字,因为await在回调函数中)。

const forEachLoop = _ => {
  console.log('Start');

  fruitsToGet.forEach(async fruit => {
    const numFruit = await getNumFruit(fruit);
    console.log(numFruit)
  });

  console.log('End')
}

我期望控制台打印以下内容:

“Start”;
“27”;
“0”;
“14”;
“End”;

但实际结果是不同的。在forEach循环中等待返回结果之前,JavaScrip先执行了 console.log('End')。

实际控制台打印如下:

‘Start’
‘End’
‘27’
‘0’
‘14’

图片描述

JavaScript 中的 forEach不支持 promise 感知,也支持 asyncawait,所以不能在 forEach 使用 await

在 map 中使用 await

如果在map中使用await, map 始终返回promise数组,这是因为异步函数总是返回promise

const mapLoop = async _ => {
  console.log('Start')
  const numFruits = await fruitsToGet.map(async fruit => {
    const numFruit = await getNumFruit(fruit);
    return numFruit;
  })
  
  console.log(numFruits);

  console.log('End')
}
      

“Start”;
“[Promise, Promise, Promise]”;
“End”;

clipboard.png

如果你在 map 中使用 awaitmap 总是返回promises,你必须等待promises 数组得到处理。 或者通过await Promise.all(arrayOfPromises)来完成此操作。



const mapLoop = async _ => {
  console.log('Start');

  const promises = fruitsToGet.map(async fruit => {
    const numFruit = await getNumFruit(fruit);
    return numFruit;
  });

  const numFruits = await Promise.all(promises);
  console.log(numFruits);

  console.log('End')
}

运行结果如下:

图片描述

如果你愿意,可以在promise 中处理返回值,解析后的将是返回的值。

const mapLoop = _ => {
  // ...
  const promises = fruitsToGet.map(async fruit => {
    const numFruit = await getNumFruit(fruit);
    return numFruit + 100
  })
  // ...
}
 
“Start”;
“[127, 100, 114]”;
“End”;


在 filter 循环中使用 await

当你使用filter时,希望筛选具有特定结果的数组。假设过滤数量大于20的数组。

如果你正常使用filter (没有 await),如下:

const filterLoop =  _ => {
  console.log('Start')

  const moreThan20 =  fruitsToGet.filter(async fruit => {
    const numFruit = await fruitBasket[fruit]
    return numFruit > 20
  })
  
  console.log(moreThan20) 
  console.log('END')
}

运行结果

Start
["apple"]
END

filter 中的await不会以相同的方式工作。 事实上,它根本不起作用。

const filterLoop = async _ => {
  console.log('Start')

  const moreThan20 =  await fruitsToGet.filter(async fruit => {
    const numFruit = fruitBasket[fruit]
    return numFruit > 20
  })
  
  console.log(moreThan20) 
  console.log('END')
}


// 打印结果
Start
["apple", "grape", "pear"]
END
 

clipboard.png

为什么会发生这种情况?

当在filter 回调中使用await时,回调总是一个promise。由于promise 总是真的,数组中的所有项都通过filter 。在filter 使用 await类以下这段代码

const filtered = array.filter(true);

filter使用 await 正确的三个步骤

  1. 使用map返回一个promise 数组
  2. 使用 await 等待处理结果
  3. 使用 filter 对返回的结果进行处理
const filterLoop = async _ => {
  console.log('Start');

  const promises = await fruitsToGet.map(fruit => getNumFruit(fruit));
 
  const numFruits = await Promise.all(promises);

  const moreThan20 = fruitsToGet.filter((fruit, index) => {
    const numFruit = numFruits[index];
    return numFruit > 20;
  })

  console.log(moreThan20);
  console.log('End')
} 

图片描述

在 reduce 循环中使用 await

如果想要计算 fruitBastet中的水果总数。 通常,你可以使用reduce循环遍历数组并将数字相加。

const reduceLoop = _ => {
  console.log('Start');

  const sum = fruitsToGet.reduce((sum, fruit) => {
    const numFruit = fruitBasket[fruit];
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}
 

运行结果:

clipboard.png

当你在 reduce 中使用await时,结果会变得非常混乱。

 const reduceLoop = async _ => {
  console.log('Start');

  const sum = await fruitsToGet.reduce(async (sum, fruit) => {
    const numFruit = await fruitBasket[fruit];
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}
 

图片描述

[object Promise]14 是什么 鬼??

剖析这一点很有趣。

  1. 在第一次遍历中,sum0numFruit27(通过getNumFruit(apple)的得到的值),0 + 27 = 27
  2. 在第二次遍历中,sum是一个promise。 (为什么?因为异步函数总是返回promises!)numFruit0.promise 无法正常添加到对象,因此JavaScript将其转换为[object Promise]字符串。 [object Promise] + 0object Promise] 0
  3. 在第三次遍历中,sum 也是一个promisenumFruit14. [object Promise] + 14[object Promise] 14

解开谜团!

这意味着,你可以在reduce回调中使用await,但是你必须记住先等待累加器!

const reduceLoop = async _ => {
  console.log('Start');

  const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
    const sum = await promisedSum;
    const numFruit = await fruitBasket[fruit];
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}

图片描述

但是从上图中看到的那样,await 操作都需要很长时间。 发生这种情况是因为reduceLoop需要等待每次遍历完成promisedSum

有一种方法可以加速reduce循环,如果你在等待promisedSum之前先等待getNumFruits(),那么reduceLoop只需要一秒钟即可完成:

const reduceLoop = async _ => {
  console.log('Start');

  const sum = await fruitsToGet.reduce(async (promisedSum, fruit) => {
    const numFruit = await fruitBasket[fruit];
    const sum = await promisedSum;
    return sum + numFruit;
  }, 0)

  console.log(sum)
  console.log('End')
}

图片描述

这是因为reduce可以在等待循环的下一个迭代之前触发所有三个getNumFruit promise。然而,这个方法有点令人困惑,因为你必须注意等待的顺序。

在reduce中使用wait最简单(也是最有效)的方法是

  1. 使用map返回一个promise 数组
  2. 使用 await 等待处理结果
  3. 使用 reduce 对返回的结果进行处理

    const reduceLoop = async _ => {
    console.log('Start');

    const promises = fruitsToGet.map(getNumFruit);
    const numFruits = await Promise.all(promises);
    const sum = numFruits.reduce((sum, fruit) => sum + fruit);

    console.log(sum)
    console.log('End')
    }

这个版本易于阅读和理解,需要一秒钟来计算水果总数。

图片描述

从上面看出来什么

  1. 如果你想连续执行await调用,请使用for循环(或任何没有回调的循环)。
  2. 永远不要和forEach一起使用await,而是使用for循环(或任何没有回调的循环)。
  3. 不要在 filterreduce 中使用 await,如果需要,先用 map 进一步骤处理,然后在使用 filterreduce 进行处理。

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

赞 92 收藏 71 评论 2

裕谷 赞了文章 · 2019-05-16

git reflog 你不知道的事

A:“一个前端小白,她对git不熟悉,辛辛苦苦加班一星期敲的代码没了。”
B:"噢?怎么没了"
A:"在终端输入git log,列出所有的commit信息,如下图:"
clipboard.png
A:“commit的信息很简单,就是做了6个功能,每个功能对应一个commit的提交,分别是feature-1 到 feature-6”
B:"好的 然后呢"
A:“然后前端小白坑爹了,执行了强制回滚,如下:”

git reset --hard 2216d4e

A:“小白回滚到了feature-1上,并且回滚的时候加了–hard,导致之前feature-2 到 feature-6的所有代码全部弄丢了,现在git log的显示如下:”

clipboard.png
A:“现在feature-2 到 feature-6的代码没了”
A:“小白还在这个基础上新添加了一个commit提交,信息叫feature-7,如下图:”

clipboard.png
A:“现在feature-2 到 feature-6全没了,还多了一个feature-7”
A:“那么B同学 请问 如何把丢失的代码feature-2 到 feature-6全部恢复回来,并且feature-7的代码也要保留”
A:“B同学,开始你的表演”
B:“啊哈哈哈!这题我会”

解答
这个问题是一个很经典很经典的git问题,其实用git reflog和git cherry-pick就能解决。
基本上掌握了git reflog和git cherry-pick,你的git命令行操作就算是成功入门了。
接下来一一讲解如何操作
你只需要在终端里输入:

git reflog

然后就会展示出所有你之前git操作,你以前所有的操作都被git记录了下来,如下图:

clipboard.png
这时候要记好两个值:4c97ff3和cd52afc,他们分别是feature-7和feature-6的hash码。然后执行回滚,回到feature-6上:

git reset --hard cd52afc

现在我们回到了feature-6上,如下图:

clipboard.png
好的,我们回到了feature-6上,但是feature-7没了,如何加上来呢?这个时候就用上了git cherry-pick,刚刚我们知道了feature-7的hash码为4c97ff3,操作如下:

git cherry-pick 4c97ff3

输入好了以后,你的feature-7的代码就回来了。期间可能会有一些冲突,按照提示解决就好。最后的结果如下图:

clipboard.png
是不是很简单,feature-1 到 feature-7的代码就合并到了一起,以前的代码也都回来了。

文章整合于微信开放社区看到的一篇有趣的文章 https://developers.weixin.qq....

查看原文

赞 18 收藏 8 评论 0

认证与成就

  • 获得 27 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-03-13
个人主页被 1.3k 人浏览