yuechen323

yuechen323 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

yuechen323 提出了问题 · 1月31日

为什么ByteArrayInputStream内部持有byte buf[]但是没有getByteArray方法

ByteArrayInputStream 是有一个构造方法的 public ByteArrayInputStream(byte buf[]) 但是为什么不一共一个 getByteArray 方法呢 ?

大家应该都用过 IOUtils.toByteArray(inputStream)

里面逻辑就是将 inputStream 里面的 byte[] write 到一个临时的 ByteArrayOutputStream, 然后再调用 ByteArrayOutputStream 的 toByteArray

如果 ByteArrayInputStream 有 getByteArray 这不就简单了吗

关注 2 回答 1

yuechen323 提出了问题 · 2019-08-20

Mybatis 能否有机制重用查询结果对象呢?大数据量查询YGC压力很大啊

4 个线程每次从表里面查询 5000 条数据, 然后插入到 es, 发现 YGC 基本 1s 一次, 当然我们的 Xmx 设置的不大, 就2G

因此同一时刻是有 4 * 5000 = 2w 条数据在 JVM 中的, 循环一次后, 2w个对象就作废了

我希望 Mybatis 可以有机制在 1 个线程内, 可以重用这 5000 个对象, 毕竟 1 个线程内是没有并发的, 不知道有没有人做过这个优化

关注 1 回答 0

yuechen323 关注了问题 · 2018-09-26

DriverManager的getConnection源码中获取conn为什么是遍历注册的driver的低效的方式呢?

DriverManager#getConnection 关键代码如下, 他的逻辑就是循环所有通过 SPI 注册进来的 driver, 挨个建立 connection, 看哪次能建立成功就直接返回

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

其实是可以通过 jdbcUrl 定位的是哪个 driver 的

java.sql.Driver#acceptsURL 其实就是做这个作用的, 我看了MySQL, PgSQL的 Driver 源码, 都是实现了这个方法, 其实上面在循环获取 connection 之前, 做这个判断就ok了

if(aDriver.driver.acceptsURL(url){
    Connection con = aDriver.driver.connect(url, info);
}

我估计是历史原因????????

关注 3 回答 1

yuechen323 提出了问题 · 2018-09-26

DriverManager的getConnection源码中获取conn为什么是遍历注册的driver的低效的方式呢?

DriverManager#getConnection 关键代码如下, 他的逻辑就是循环所有通过 SPI 注册进来的 driver, 挨个建立 connection, 看哪次能建立成功就直接返回

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

其实是可以通过 jdbcUrl 定位的是哪个 driver 的

java.sql.Driver#acceptsURL 其实就是做这个作用的, 我看了MySQL, PgSQL的 Driver 源码, 都是实现了这个方法, 其实上面在循环获取 connection 之前, 做这个判断就ok了

if(aDriver.driver.acceptsURL(url){
    Connection con = aDriver.driver.connect(url, info);
}

我估计是历史原因????????

关注 3 回答 1

yuechen323 赞了文章 · 2018-04-24

钉钉微应用 开发体验及心得

开始调研

主要还是看钉钉的官方文档和论坛,钉钉推荐使用saltUI和saltRouter进行开发微应用,但是看了这2个东西的源码后,感觉没封装什么东西,主要的功能还是集中在钉钉的dingtalk.js中,所以个人认为没必要用它推荐的这2个东西。

框架选择

作为一个vue的忠实粉丝,这次也肯定想用vue进行开发,考虑到vue2.0还没有很好的移动端UI框架,所以这次试用了更成熟的vue1.0的ui框架vux,然后微应用其实对应用状态管理的要求还是比较高的,所以也用了vuex来管理应用状态。

微应用流程

我们使用的是免登陆流程如下

  1. 它的入口网页会自动传appid,corpid,suiteKey这3个参数在url上面,通过这3个参数去自己的服务器获取到dd.config需要用的参数.

  2. 然后在dd.ready中设置和获取一些全局的钉钉属性,比如 设置左上角返回键的回调,获取容器的版本信息(用来判断能不能调用某些jsapi)

  3. 再初始化vue,配置vue插件什么的

  4. 最后判断js.config是否成功,然后dispath到vuex中

代码详解

对比上面流程的1. 2. 3. 4. 的代码

getConfig() //1.通过url上的3个参数,获取自己服务器上的配置信息
    .then((data)=>{ 
        ddConfig = data;
        dd.config(ddConfig); //1.初始化钉钉的jssdk
    })
    .then(ddIsReady) //2.做一些钉钉的全局设置
    .then(initVue) //3.初始化vue
    .then(()=>{
        document.querySelector('#init-loading').remove(); //移除加载动画
        console.log('init vue 完成')
        setTimeout(()=>{
            if(ddConfig != null){
                commit('DDCONFIG_SUCCESS', ddConfig)  //4.通知vuex改变应用功能状态
            }else{
                commit('DDCONFIG_ERROR', false);  //4.通知vuex改变应用功能状态
            }
        },300)
    })

getConfig

function getConfig() {
    return Q.Promise((success, error)=>{
        axios.get(env.API_HOST+'/auth/getConfig', {
            params: {
                corpid: getParamByName('corpid')||'',
                appid: getParamByName('appid')||'',
                suitekey: getParamByName('suiteKey')||'',
                paramUrl: document.URL
            },
            timeout: 2000,
        }).then(function (response) {
            if(response.status == 200 && response.data.code == 200){
                let res = response.data.result;
                let ddConfig = {
                    agentId: res.agentId, // 必填,微应用ID
                    corpId: res.corpId,//必填,企业ID
                    timeStamp: res.timeStamp, // 必填,生成签名的时间戳
                    nonceStr: res.nonceStr, // 必填,生成签名的随机串
                    signature: res.signature, // 必填,签名
                    type:0,   //选填。0表示微应用的jsapi,1表示服务窗的jsapi。不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持
                    jsApiList : [
                        'runtime.info',
                        'runtime.permission.requestAuthCode',
                        'runtime.permission.requestOperateAuthCode', //反馈式操作临时授权码

                        'biz.alipay.pay',
                        'biz.contact.choose',
                        'biz.contact.complexChoose',
                        'biz.contact.complexPicker',
                        'biz.contact.createGroup',
                        'biz.customContact.choose',
                        'biz.customContact.multipleChoose',
                        'biz.ding.post',
                        'biz.map.locate',
                        'biz.map.view',
                        'biz.util.openLink',
                        'biz.util.open',
                        'biz.util.share',
                        'biz.util.ut',
                        'biz.util.uploadImage',
                        'biz.util.previewImage',
                        'biz.util.datepicker',
                        'biz.util.timepicker',
                        'biz.util.datetimepicker',
                        'biz.util.chosen',
                        'biz.util.encrypt',
                        'biz.util.decrypt',
                        'biz.chat.pickConversation',
                        'biz.telephone.call',
                        'biz.navigation.setLeft',
                        'biz.navigation.setTitle',
                        'biz.navigation.setIcon',
                        'biz.navigation.close',
                        'biz.navigation.setRight',
                        'biz.navigation.setMenu',
                        'biz.user.get',

                        'ui.progressBar.setColors',

                        'device.base.getInterface',
                        'device.connection.getNetworkType',
                        'device.launcher.checkInstalledApps',
                        'device.launcher.launchApp',
                        'device.notification.confirm',
                        'device.notification.alert',
                        'device.notification.prompt',
                        'device.notification.showPreloader',
                        'device.notification.hidePreloader',
                        'device.notification.toast',
                        'device.notification.actionSheet',
                        'device.notification.modal',
                        'device.geolocation.get',


                    ] // 必填,需要使用的jsapi列表,注意:不要带dd。
                }
                success(ddConfig)
            }else{
                error({errCode:-2,msg:'接口请求失败'})
            }
        }).catch(function (err) {
            error({errCode:-2,msg:'接口请求失败'})
        });
    })

}

ddIsReady

function ddIsReady() {
    return Q.Promise((success, error)=>{
        let timeout = setTimeout(()=>{
            error({errCode:-1,msg:'dd.ready初始化超时'});
        },2000)
        dd.ready(function(){
            console.log('初始化钉钉');
            clearTimeout(timeout)

            //设置返回按钮
            dd.biz.navigation.setLeft({
                show: true,//控制按钮显示, true 显示, false 隐藏, 默认true
                control: true,//是否控制点击事件,true 控制,false 不控制, 默认false
                showIcon: true,//是否显示icon,true 显示, false 不显示,默认true; 注:具体UI以客户端为准
                text: '返回',//控制显示文本,空字符串表示显示默认文本
                onSuccess : function(result) {
                    //如果control为true,则onSuccess将在发生按钮点击事件被回调
                    console.log('点击了返回按钮');
                    window.history.back();
                },
                onFail : function(err) {}
            });
            //获取容器信息
            dd.runtime.info({
                onSuccess: function(result) {
                    window.ability = parseInt(result.ability.replace(/\./g,''));
                    console.log('容器版本为'+window.ability)
                },
                onFail : function(err) {}
            })

            success(true)
        });
        dd.error(function(err){
            clearTimeout(timeout)
            /**
             {
                message:"错误信息",//message信息会展示出钉钉服务端生成签名使用的参数,请和您生成签名的参数作对比,找出错误的参数
                errorCode:"错误码"
             }
             **/
            console.error('dd error: ' + JSON.stringify(err));
            error({errCode:-1,msg:'dd.error配置信息不对'})
        });
    })
}

initVue

function initVue() {
    return Q.Promise((success, error)=>{

        Vue.use(Router)
        Vue.use(bbPlugin)
        Vue.use(ddPlugin)

        let router = new Router({
            transitionOnLoad: false
        })
        router.map({
            [env.BASE_PATH] : {
                component: function(resolve){
                    require.ensure([], function() {
                        let route = require('./page/home/route').default;
                        resolve(route);
                    },'home')
                },
                subRoutes: {
                    '/': {
                        component: function (resolve) {
                            require.ensure([], function () {
                                let route = require('./page/home/index/route').default;
                                resolve(route);
                            },'index')
                        }
                    },
                    '/member' : {
                        component: function(resolve){
                            require.ensure([], function() {
                                let route = require('./page/home/member/route').default;
                                resolve(route);
                            },'member')
                        }
                    },
                }
            },
            [env.BASE_PATH+'/user/sign_in'] : {
                component: function (resolve) {
                    require.ensure([], function () {
                        let route = require('./page/user-sign-in/route').default;
                        resolve(route);
                    }, 'user-sign-in')
                }
            },
            [env.BASE_PATH+'/user/bind'] : {
                component: function (resolve) {
                    require.ensure([], function () {
                        let route = require('./page/user-bind-mobile/route').default;
                        resolve(route);
                    }, 'user-bind-mobile')
                }
            }
        });
        router.redirect({
            '*': env.BASE_PATH
        });
        let history = window.sessionStorage
        history.clear()
        let historyCount = history.getItem('count') * 1 || 0
        history.setItem('/', 0)

        router.beforeEach(({ to, from, next }) => {
            const toIndex = history.getItem(to.path)
            const fromIndex = history.getItem(from.path)
            if (toIndex) {
                if (toIndex > fromIndex || !fromIndex) {
                    commit('UPDATE_DIRECTION', 'forward')
                } else {
                    commit('UPDATE_DIRECTION', 'reverse')
                }
            } else {
                ++historyCount
                history.setItem('count', historyCount)
                to.path !== '/' && history.setItem(to.path, historyCount)
                commit('UPDATE_DIRECTION', 'forward')
            }
            commit('UPDATE_LOADING', true)


            setTimeout(()=>{
                try {
                    //设置右侧按钮
                    dd.biz.navigation.setRight({
                        show: false,//控制按钮显示, true 显示, false 隐藏, 默认true
                    });
                }catch (err){
                    console.error(err);
                }

                next();
            }, 10)
        })
        router.afterEach(() => {
            commit('UPDATE_LOADING', false)
        })
        sync(store, router)
        router.start(App, '#app')

        FastClick.attach(document.body)

        success()
    })
}

然后就可以愉快的写业务代码啦

怎么能不开源呢?我只是把很多开源项目的代码拼到了一起,请谅解... github demo

查看原文

赞 8 收藏 14 评论 1

yuechen323 提出了问题 · 2018-04-17

钉钉免登陆的这几个步骤, 在钉钉的后端服务都是怎么实现的, 有没有人想过?

用户打开钉钉首页, 点击某一个应用的图标, 进入到企业自己应用的h5页面, 经历的步骤还是很多的, 如下:

  1. 注册企业, 得到corpId和CorpSecret
  2. 应用后端服务: 用corpId和CorpSecret获取accessToken
  3. 应用后端服务: 用accessToken获取jsTicket
  4. 应用后端服务: 对 ’url’,‘nonceStr’,‘agentId’,‘timeStamp’,‘CorpId’ 签名, 得到 signature, 并返回前端
  5. 应用前端页面: 用上一步的数据执行dd.config进行验证
  6. 应用前端页面: 执行 dd.runtime.permission.requestAuthCode 得到 code, 然后传给后端
  7. 应用后端服务: 用 code 和 accessToken 获取 userId
  8. 应用后端服务: 用 userId 和 accessToken 获取 userInfo

??????????????????

步骤2: 钉钉后台是不是走了一个密码认证的一个过程, 然后 redis 中存储 这个 accessToken, 并设置 expire = 2h

步骤5: 这步骤好像很关键, 获取的 code 就可以对应上用户了, 钉钉后台貌似是redis存 key:code, value: userId , 有些迷糊, dd.config 和 dd.ready

关注 2 回答 1

yuechen323 提出了问题 · 2018-03-10

vuejs的服务器端渲染和java的服务器端渲染有什么区别吗?

在刚工作做java开发的时候, 全都是服务器端渲染, 从 jsp 到 freemarker 等, 为什么 vue/react 要单独提出来这个概念呢? 与 vue/react 有关系吗?

java的服务器端渲染
以使用Spring为例, 就是写个 Controller, 然后 return 一个模板引擎页面, 同时需要往模板页面中的变量设置值

vue的服务器端渲染
看官网是结合Express这个web框架, 原理也都差不多, 官网是这样的

// 第 1 步:创建一个 Vue 实例
const Vue = require('vue')
const app = new Vue({
  template: `<div>Hello World</div>`
})
// 第 2 步:创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer()
// 第 3 步:将 Vue 实例渲染为 HTML
renderer.renderToString(app, (err, html) => {
  if (err) throw err
  console.log(html)
  // => <div data-server-rendered="true">Hello World</div>
})

纯 Express 是这样

app.get('/', function (req, res) {
  res.render('index', { title: 'Hey', message: 'Hello there!'});
});

我的理解是, js技术栈中服务器渲染用 Express 就已经足够了, 为什么 Vue, React 还要单独开发一个 SSR 模块, 实质不就是访问一个 url, 然后 Server 端直接返回一个页面吗? 用 java 不行吗? 模板中你想引入啥 js 框架不就这么写就 ok 了嘛?

// template.tpl
<html>
<body>
<ul>
<% for(String msg : msgs){ %>
<li><%=msg%></li>
%>
</ul>
<script data-original="https://cdn.jsdelivr.net/npm/vue"></script>
</body>
</html>

请大家指教

关注 2 回答 2

yuechen323 关注了问题 · 2018-03-10

解决为啥{}.toString()会报错

{}.toString()//会报错

({}).toString()//不报错
[].toString()//不报错
var a= {}; a.toString()//不报错

求解?

大家回答都是对的,感谢!

关注 8 回答 5

认证与成就

  • 获得 37 次点赞
  • 获得 122 枚徽章 获得 5 枚金徽章, 获得 51 枚银徽章, 获得 66 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-01-29
个人主页被 1.6k 人浏览