2018年9月

springboot2.x整合redis(含Cacheable)

2.x整合redis发生了一些变化,下面给出整合redis的方式
pom

<!--集成redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <!-- 要用redis连接池 必须有pool依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>

        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.36</version>
        </dependency>

常规的redis配置

spring.redis.host=192.168.1.95
# Redis服务器连接端口
spring.redis.port=6379
spring.redis.database=0
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=30000

设置redis template

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
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.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 * @author: ciika
 * @date: 2018-07-11
 */
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用fastjson序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        return template;
    }
}

所依赖的FastJsonRedisSerializer

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;
/**

 * @date: 2018-09-29
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Class<T> clazz;
    public FastJsonRedisSerializer(Class<T> clazz){
        super();
        this.clazz = clazz;
    }
    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (null == t){
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (null == bytes || bytes.length <=0){
            return null;
        }
        String str = new String(bytes,DEFAULT_CHARSET);
        
        //解决com.alibaba.fastjson.JSONException: autoType is not support
        ParserConfig.getGlobalInstance().addAccept("com.entity.CatProduct");
        return JSON.parseObject(str,clazz);
    }
}

现在RedisTemplate就可以使用了
如果要使用Cacheable
则需要加另外的配置

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * author ciika
 * date 2018/9/29
 */
@Configuration
@EnableCaching
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfiguration extends CachingConfigurerSupport{

    private Logger logger = LoggerFactory.getLogger(this.getClass());



    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory redisConnectionFactory) {
        logger.info("cacheManager注入成功");
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
        return redisCacheManager;
    }

    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> getRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        logger.info("redisTemplatet注入成功");
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 设置value的序列化规则和 key的序列化规则
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setKeySerializer(new StringRedisSerializer());


        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }


}

ubunut添加用户生成sshkey添加sudo权限

先创建公匙(.pub结尾),用户写入到authorized_keys,私匙是自己用
ssh-keygen
可以全部置空
创建用户
useradd 用户名
passwd 用户名
然后在/home目录下就会为该新用户创建一个主目录,在主目录中创建.ssh目录,然后在.ssh目录下创建authorized_keys文件,在文件中将用户的公钥添加进去,然后用户就可以用密钥登陆了。
然后把 公匙的内容写入到authorized_keys,之后权限设置为600
然后把私匙下载下来,作为ssh key就可以了
为用户指定shell脚本,修改/etc/passwd
为用户添加sudo权限
vim /etc/group
在adm和sudo行之后加上新建的用户名就好了

/etc/passwd详解:
https://www.cyberciti.biz/faq/understanding-etcpasswd-file-format/
https://blog.csdn.net/u011534057/article/details/51679358

facebook instant games 添加智能助手

facebook instant games 添加智能助手,用于召回用户,例如多久没登录了,可在后台发送消息以提升日活和留存。

1 將 Messenger 平台新增至 Facebook
在应用设定边栏中,点击「产品」下方的「+ 新增产品」。
将滑鼠移到「Messenger」上方以显示选项。点击「设定」按钮。Messenger 平台便会新增至应用,然後显示 Messenger 设定主控台。

2 创建粉丝页
创建的时候有四个地方一定要注意,不然会造成收不到回调消息的问题,在选类型的时候一定要选web page(应用主页),第二是名字要包含游戏的名字,最好是一样,第三是一定要在instant games下面的details里面设置主页关联(App Page),第四是粉丝页一定要设置为上线状态。这些都设置好之后messenger会由灰色变成绿色。
a1.png

a2.png

2 准备webhook
分为两部分,第一部分是webhook验证,采用的是get,第二部分用于接收webhook的回调,采用的是Post,以下是python flask代码

@app.route('/webhook',methods=['GET'])
def webhook():
    print("webhook called")
    challenge=request.args.get('hub.challenge')
    print(challenge)
    return str(challenge)

@app.route('/webhook',methods=['POST'])
def webhookpost():
    try:
        print("webhook called POST")
        # print(request.data)
        jsonObj = json.loads(request.get_data().decode())
        print(jsonObj)
        if jsonObj is not None:
            for item in jsonObj["entry"]:
                print(item)
                msg = item["messaging"][0]
                print(msg)
                print(msg["sender"]["id"])
                recipient = msg["sender"]["id"]
                context = msg["game_play"]["context_id"]
                player_id = msg["game_play"]["player_id"]
    except Exception as err:
        print(err)
        pass
    return ""

3 设定应用的 Webhook
在 Messenger 设定主控台的「Webhooks」区段中,点击「设定 Webhooks」按钮。
输入 Webhook 的公开网址。即在准备webhook里面在服务端准备好的网址填入进去,例如:https://fb.ciika.com/webhook
输入 Webhook 的验证token。
在「订阅栏位」下方,选择想要传递到 Webhook 的 Webhook 事件。建议您一开始至少选择 messages、messaging_postbacks、messaging_postbacks,建议一定要选这三个,不然在messenger的安卓客户端可能会收不到回调消息。
点击「验证并储存」按钮。
Messenger 平台会向 Webhook 传送 GET 要求,同时随附您提供的验证权杖。如果 Webhook 有效且已正确设定来回应验证要求,系统将会储存 Webhook 设定。

a3.png

4 将应用订阅至 Facebook 粉丝专页
在 Messenger 设定主控台的「权杖产生」区段,点击「选择粉丝专页」下拉式功能表,然後选择您要这个应用订阅的 Facebook 粉丝专页。这个粉丝专页就是当用户在 Messenger 与其交谈时,您想要 Webhook 接收事件的粉丝专页。
复制「粉丝专页存取权杖」栏位中显示的权杖。稍後您会使用这个权杖发出 API 要求。
在 Messenger 设定主控台的「Webhook」区段,点击「选择粉丝专页」下拉式功能表,然後选择您先前为其产生粉丝专页存取权杖的同一 Facebook 粉丝专页。如此会订阅您的应用,以接收该粉丝专页的 Webhook 事件。
点击下拉式功能表旁的「订阅」按钮。
Messenger 平台现在就能够针对所选粉丝专页,将订阅的 Webhook 事件传送到 Webhook。
a4.png

5 测试应用订阅
通过postman或者其它测试工具均可以测试:

{
  "messaging_type": "UPDATE",
  "recipient": {
    "id": "2306315372728440"
  },
  "message": {
    "attachment": {
      "type": "template",
      "payload": {
        "template_type": "generic",
        "elements": [
          {
            "title": "It has been a while since your last game. Time to get back",
            "image_url":"https://ciika.oss-cn-shenzhen.aliyuncs.com/test/fb/common/loginshare.png",
            "buttons": [
              {
                "type": "game_play",
                "title": "ciika test.",
                "payload": "{}"
              }
            ]
          }
        ]
      }
    }
  }
}

注意recipient里面的id一定要填webhook回调的sender的id,即 msg["sender"]["id"],不然会报错No matching user found,另外token也要对,不然会报token的错误。
正确的回调可能会打印以下部分,不同的回调结构会有不同

{'object': 'page', 'entry': [{'id': '728775707473354', 'messaging': [{'recipient': {'id': '728775707473634'}, 'sender': {'id': '2306315372728440'}, 'timestamp': 1536834804892, 'game_play': {'context_type': 'THREAD', 'player_id': '1897049693710005', 'game_id': '226416793698456', 'context_id': '1895615070526821'}}], 'time': 1536834804892}]}

附上python 发送消息的例子

import requests
import json


def send(recipient):
    body ={
      "messaging_type": "UPDATE",
      "recipient": {
        "id": recipient
      },
      "message": {
        "attachment": {
          "type": "template",
          "payload": {
            "template_type": "generic",
            "elements": [
              {
                "title": "facebook instant games webhook test",
                "image_url":"https://ciika.com/img.png,
                "buttons": [
                  {
                    "type": "game_play",
                    "title": "Play",
                    "payload": "{}"
                  }
                ]
              }
            ]
          }
        }
      }
    }
    url='https://graph.facebook.com/v2.6/me/messages?access_token=EAAD1wRnZBdYEBAJq73nqvATf0GQJWeDsVwIKnnww7JzpG2QoV80R0v48TJhJFUBmtFIde6av4bVZA6aZAqHovxiw2zNfbBZA1XmVLvbJMj12OIGCHUE677H07GhkZBSXbIRTLqPHAH3R8E8xqdDH7iB9gMNFEdRDIWAZC0ZBofui8UDBZC9ZBSvx4PXZCMm8wj17112'
    headers = {"Content-type": "application/json"}
    data = json.dumps(body)
    # data=bytes(data,'utf8')
    headers = {'Content-Type': 'application/json'}    
    response = requests.post(url=url, headers=headers, data=data)    

官方参考:https://developers.facebook.com/docs/games/instant-games/getting-started/quickstart?locale=zh_CN

cocos creator在facebook里面图像和文字模糊锯齿严重

cocos creator在facebook instant games里面图像和文字模糊锯齿严重,无论是放大或者是缩小图片都无法解决,网上目前给出的解决方案例如抗锯齿,设置倍数,均无法解决。
解决方案有两个。

一、方案1:修改源代码:
1:找到cocos creator的目录
例如我的目录是:C:\CocosCreator2
2:修改_retinaEnabled
进入到:C:\CocosCreator2\resources\engine\cocos2d\core\platform
找到CCView.js
打开并修改
// Retina disabled by default
_t._retinaEnabled = false;
把false改成true
1.png
3:修改ENABLE_WEBGL_ANTIALIAS
进入到:C:\CocosCreator2\resources\engine\cocos2d\core\platform
找到CCView.js
打开并修改
ENABLE_WEBGL_ANTIALIAS: false,
把false修改成true
2.png

4:关闭项目,重启,重新打包,锯齿问题会解决

二、方案2:直接设置
cc.view.enableRetina(true) ;
cc.view.resizeWithBrowserSize(true);

我目前是通过第一个方案去解决的,推荐使用方案二。

facebook instant games 智能助手常见错误

1:No matching user found
仔细检查recipient和context_id是否设置错误
2:Cannot send Instant Game message to user at this time
这是由于发送达到策略的限制,这个时候需要对方去点击一下消息
3:只能助手审核
“小游戏”设置的智能助手还未经过审核
小游戏设置的 Messenger 智能助手还未经过审核。你可以在 Messenger 功能选项中把智能助手提交审核。
智能助手未经过审核不影响游戏上线,只是没有推送和召回等信息。