前后端分离架构中的接口安全(上篇)_前后端分离 安全设计-程序员宅基地

技术标签: java  

互联网发展至今,已由传统的前后端统一架构演变为如今的前后端分离架构,最初的前端网页大多由JSP、ASP、PHP等动态网页技术生成,前后端十分耦合,也不利于扩展。现在的前端分支很多,如:Web前端、Android端、IOS端,甚至还有物联网等。前后端分离的好处就是后端只需要实现一套界面,所有前端即可通用。
前后端的传输通过HTTP进行传输,也带来了一些安全问题,如果抓包、模拟请求、洪水攻击、参数劫持、网络爬虫等等。如何对非法请求进行有效拦截,保护合法请求的权益是这篇文章需要讨论的。
作者依据多年互联网后端开发经验,总结出了以下提升网络安全的方式:

  • 采用HTTPS协议

  • 密钥存储到服务端而非客户端,客户端应从服务端动态获取密钥

  • 请求隐私接口,利用token机制校验其合法性

  • 对请求参数进行合法性校验

  • 对请求参数进行签名认证,防止参数被篡改

  • 对输入输出参数进行加密,客户端加密输入参数,服务端加密输出参数

那么,下面我将对以上方式展开做详细说明。

HTTP VS HTTPS

普通的HTTP协议是以明文形式进行传输,不提供任何方式的数据加密,很容易解读传输报文。而HTTPS协议在HTTP基础上加入了SSL层,而SSL层通过证书来验证服务器的身份,并为浏览器和服务器之间的通信加密,保护了传输过程中的数据安全。

动态密钥的获取

对于可逆加密算法,是需要通过密钥进行加解密,如果直接放到客户端,那么很容易反编译后拿到密钥,这是相当不安全的做法,因此考虑将密钥放到服务端,由服务端提供接口,让客户单动态获取密钥,具体做法如下:
1、客户端先通过RSA算法生成一套客户端的公私钥对(clientPublicKey和clientPrivateKey)
2、调用getRSA接口,服务端会返回serverPublicKey
3、客户端拿到serverPublicKey后,用serverPublicKey作为公钥,clientPublicKey作为明文对clientPublicKey进行RSA加密,调用getKey接口,将加密后的clientPublicKey传给服务端,服务端接收到请求后会传给客户端RSA加密后的密钥
4、客户端拿到后以clientPrivateKey为私钥对其解密,得到最终的密钥,此流程结束。
(注:上述提到的所以数据均不能保存到文件里,必须保存到内存中,因为只有保存到内存中,黑客才拿不到这些核心数据,所以每次使用获取的密钥前先判断内存中的密钥是否存在,不存在,则需要获取。)
为了便于理解,我画了一个简单的流程图:
这里写图片描述
那么具体是如何实现的呢,请看代码:

#全局密钥配置,所以加密算法统一密钥
api:
  encrypt:
    key: d7b85c6e414dbcda
#此配置的公司钥信息为测试数据,不能直接使用,请自行重新生成公私钥
rsa:
  publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcZlkHaSN0fw3CWGgzcuPeOKPdNKHdc2nR6KLXazhhzFhe78NqMrhsyNTf3651acS2lADK3CzASzH4T0bT+GnJ77joDOP+0SqubHKwAIv850lT0QxS+deuUHg2+uHYhdhIw5NCmZ0SkNalw8igP1yS+2TEIYan3lakPBvZISqRswIDAQAB
  privateKey: MIICeAIBADANBgkqhkiG9w0BAQeFAcSCAmIwggJeAgEAAoGBAJxmWQdpI3R/DcJYaDNy4944o900od1zadHootdrOGHMWF7vw2oyuGzI1N/frmxoVLaUAMrcLMBLMfhPRtP4acnvuOgM4/7RKq5scrAAi/znSVPRDFL5165QeDb64diF2EjDk0KZnRKQ1qXDyKA/XJL7ZMQhhqfeVqQ8G9khKpGzAgMBAAECgYEAj+5AkGlZj6Q9bVUez/ozahaF9tSxAbNs9xg4hDbQNHByAyxzkhALWVGZVk3rnyiEjWG3OPlW1cBdxD5w2DIMZ6oeyNPA4nehYrf42duk6AI//vd3GsdJa6Dtf2has1R+0uFrq9MRhfRunAf0w6Z9zNbiPNSd9VzKjjSvcX7OTsECQQD20kekMToC6LZaZPr1p05TLUTzXHvTcCllSeXWLsjVyn0AAME17FJRcL9VXQuSUK7PQ5Lf5+OpjrCRYsIvuZg9AkEAojdC6k3SqGnbtftLfGHMDn1fe0nTJmL05emwXgJvwToUBdytvgbTtqs0MsnuaOxMIMrBtpbhS6JiB5Idb7GArwJAfKTkmP5jFWT/8dZdBgFfhJGv6FakEjrqLMSM1QT7VzvStFWtPNYDHC2b8jfyyAkGvpSZb4ljZxUwBbuh5QgM4QJBAJDrV7+lOP62W9APqdd8M2X6gbPON3JC09EW3jaObLKupTa7eQicZsX5249IMdLQ0A43tanez3XXo0ZqNhwT8wcCQQDUubpNLwgAwN2X7kW1btQtvZW47o9CbCv+zFKJYms5WLrVpotjkrCgPeuloDAjxeHNARX8ZTVDxls6KrjLH3lT
 <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
public class AesEncryptUtils {
    

    private static final String KEY = "d7585fde114abcda";
    private static final String ALGORITHMSTR = "AES/CBC/NoPadding";

    public static String base64Encode(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    public static byte[] base64Decode(String base64Code) throws Exception {
        return Base64.decodeBase64(base64Code);
    }

    public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(128);
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
        return cipher.doFinal(content.getBytes("utf-8"));
    }

    public static String aesEncrypt(String content, String encryptKey) throws Exception {
        return base64Encode(aesEncryptToBytes(content, encryptKey));
    }

    public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(128);
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
        byte[] decryptBytes = cipher.doFinal(encryptBytes);
        return new String(decryptBytes);
    }

    public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
        return aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
    }

    public static void main(String[] args) throws Exception {
        String content = "{name:\"lynn\",id:1}";
        System.out.println("加密前:" + content);

        String encrypt = aesEncrypt(content, KEY);
        System.out.println(encrypt.length() + ":加密后:" + encrypt);

        String decrypt = aesDecrypt("H9pGuDMV+iJoS8YSfJ2Vx0NYN7v7YR0tMm1ze5zp0WvNEFXQPM7K0k3IDUbYr5ZIckTkTHcIX5Va/cstIPrYEK3KjfCwtOG19l82u+x6soa9FzAtdL4EW5HAFMmpVJVyG3wz/XUysIRCwvoJ20ruEwk07RB3ojc1Vtns8t4kKZE=", "d7b85f6e214abcda");
        System.out.println("解密后:" + decrypt);
    }
}
public class RSAUtils {
    

    public static final String CHARSET = "UTF-8";
    public static final String RSA_ALGORITHM = "RSA";


    public static Map<String, String> createKeys(int keySize){
        //为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator kpg;
        try{
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        }catch(NoSuchAlgorithmException e){
            throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
        }

        //初始化KeyPairGenerator对象,密钥长度
        kpg.initialize(keySize);
        //生成密匙对
        KeyPair keyPair = kpg.generateKeyPair();
        //得到公钥
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());
        //得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());
        Map<String, String> keyPairMap = new HashMap<>(2);
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);

        return keyPairMap;
    }

    /**
     * 得到公钥
     * @param publicKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通过X509编码的Key指令获得公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
        RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
        return key;
    }

    /**
     * 得到私钥
     * @param privateKey 密钥字符串(经过base64编码)
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通过PKCS#8编码的Key指令获得私钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
        RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
        return key;
    }

    /**
     * 公钥加密
     * @param data
     * @param publicKey
     * @return
     */
    public static String publicEncrypt(String data, RSAPublicKey publicKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
        }catch(Exception e){
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateDecrypt(String data, RSAPrivateKey privateKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET);
        }catch(Exception e){
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥加密
     * @param data
     * @param privateKey
     * @return
     */

    public static String privateEncrypt(String data, RSAPrivateKey privateKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
        }catch(Exception e){
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 公钥解密
     * @param data
     * @param publicKey
     * @return
     */

    public static String publicDecrypt(String data, RSAPublicKey publicKey){
        try{
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET);
        }catch(Exception e){
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize){
        int maxBlock = 0;
        if(opmode == Cipher.DECRYPT_MODE){
            maxBlock = keySize / 8;
        }else{
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try{
            while(datas.length > offSet){
                if(datas.length-offSet > maxBlock){
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                }else{
                    buff = cipher.doFinal(datas, offSet, datas.length-offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        }catch(Exception e){
            throw new RuntimeException("加解密阀值为["+maxBlock+"]的数据时发生异常", e);
        }
        byte[] resultDatas = out.toByteArray();
        IOUtils.closeQuietly(out);
        return resultDatas;
    }

    public static void main(String[] args) throws Exception{
        Map<String, String> keyMap = RSAUtils.createKeys(1024);
        String  publicKey = keyMap.get("publicKey");
        String  privateKey = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAJxmWQdpI3R/DcJYaDNy4944o900od1zadHootdrOGHMWF7vw2oyuGzI1N/frmxoVLaUAMrcLMBLMfhPRtP4acnvuOgM4/7RKq5scrAAi/znSVPRDFL5165QeDb64diF2EjDk0KZnRKQ1qXDyKA/XJL7ZMQhhqfeVqQ8G9khKpGzAgMBAAECgYEAj+5AkGlZj6Q9bVUez/ozahaF9tSxAbNs9xg4hDbQNHByAyxzkhALWVGZVk3rnyiEjWG3OPlW1cBdxD5w2DIMZ6oeyNPA4nehYrf42duk6AI//vd3GsdJa6Dtf2has1R+0uFrq9MRhfRunAf0w6Z9zNbiPNSd9VzKjjSvcX7OTsECQQD20kekMToC6LZaZPr1p05TLUTzXHvTcCllSeXWLsjVyn0AAME17FJRcL9VXQuSUK7PQ5Lf5+OpjrCRYsIvuZg9AkEAojdC6k3SqGnbtftLfGHMDn1fe0nTJmL05emwXgJvwToUBdytvgbTtqs0MsnuaOxMIMrBtpbhS6JiB5Idb7GArwJAfKTkmP5jFWT/8dZdBgFfhJGv6FYkEjrqLMSM1QT7VzvStFWtPNYDHC2b8jfyyAkGvpSZb4ljZxUwBbuh5QgM4QJBAJDrV7+lOP62W9APqdd8M2X6gbPON3JC09EW3jaObLKupTa7eQicZsX5249IMdLQ0A43tanez3XXo0ZqNhwT8wcCQQDUubpNLwgAwN2X7kW1btQtvZW47o9CbCv+zFKJYms5WLrVpotjkrCgPeuloDAjxeHNARX8ZTVDxls6KrjLH3lT";
        System.out.println("公钥: \n\r" + publicKey);
        System.out.println("私钥: \n\r" + privateKey);

        System.out.println("公钥加密——私钥解密");
        String str = "站在大明门前守卫的禁卫军,事先没有接到\n" +
                "有关的命令,但看到大批盛装的官员来临,也就\n" +
                "以为确系举行大典,因而未加询问。进大明门即\n" +
                "为皇城。文武百官看到端门午门之前气氛平静,\n" +
                "城楼上下也无朝会的迹象,既无几案,站队点名\n" +
                "的御史和御前侍卫“大汉将军”也不见踪影,不免\n" +
                "心中揣测,互相询问:所谓午朝是否讹传?";
        System.out.println("\r明文:\r\n" + str);
        System.out.println("\r明文大小:\r\n" + str.getBytes().length);
        String encodedData = RSAUtils.publicEncrypt(str, RSAUtils.getPublicKey(publicKey));
        System.out.println("密文:\r\n" + encodedData);
        String decodedData = RSAUtils.privateDecrypt("X4hHPa9NjPd5QJGPus+4+hWmOzbWg7oCJ1+Vc+7dHW81nEhkYnJpFyV5xcDkg70N2Mym+YAJ1PvYY9sQWf9/EkUE61TpUKBmDaGWLjEr3A1f9cKIelqLKLsJGdXEOr7Z55k4vYFvA7N3Vf5KQo3NrouvIT4wR+SjH4tDQ8tNh3JH8BvXLtXqGa2TCK2z1AzHNgYzcLCrqDasd7UDHRPZPiW4thktM/whjBn0tU9B/kKjAjLuYttKLEmy5nT7v7u16aZ6ehkk+kzvuCXF%2B3RsqraISDPbsTki2agJyqsycRx3w7CvKRyUbZhFaNcWigOwmcbZVoiom+ldh7Vh6HYqDA==", RSAUtils.getPrivateKey(privateKey));
        System.out.println("解密后文字: \r\n" + decodedData);

    }
}
/**
 * 私钥输入参数(其实就是客户端通过服务端返回的公钥加密后的客户端自己生成的公钥)
 */
public class KeyRequest {
    

    /**
     * 客户端自己生成的加密后公钥
     */
    @NotNull
    private String clientEncryptPublicKey;

    public String getClientEncryptPublicKey() {
        return clientEncryptPublicKey;
    }

    public void setClientEncryptPublicKey(String clientEncryptPublicKey) {
        this.clientEncryptPublicKey = clientEncryptPublicKey;
    }
}
/**
 * RSA生成的公私钥输出参数
 */
public class RSAResponse extends BaseResponse{
    

    private String serverPublicKey;

    private String serverPrivateKey;

    public static class Builder{
    
        private String serverPublicKey;

        private String serverPrivateKey;

        public Builder setServerPublicKey(String serverPublicKey){
            this.serverPublicKey = serverPublicKey;
            return this;
        }

        public Builder setServerPrivateKey(String serverPrivateKey){
            this.serverPrivateKey = serverPrivateKey;
            return this;
        }

        public RSAResponse build(){
            return new RSAResponse(this);
        }

    }

    public static Builder options(){
        return new Builder();
    }

    public RSAResponse(Builder builder){
        this.serverPrivateKey = builder.serverPrivateKey;
        this.serverPublicKey = builder.serverPublicKey;
    }

    public String getServerPrivateKey() {
        return serverPrivateKey;
    }

    public String getServerPublicKey() {
        return serverPublicKey;
    }
}
/**
 * 私钥输出参数
 */
public class KeyResponse extends BaseResponse{
    

    /**
     * 整个系统所有加密算法共用的密钥
     */
    private String key;

    public static class Builder{
    
        private String key;

        public Builder setKey(String key){
            this.key = key;
            return this;
        }

        public KeyResponse build(){
            return new KeyResponse(this);
        }
    }

    public static Builder options(){
        return new Builder();
    }

    private KeyResponse(Builder builder){
        this.key = builder.key;
    }

    public String getKey() {
        return key;
    }

}
/**
 * API传输加解密相关接口
 */
public interface EncryptOpenService {
    

    /**
     * 生成RSA公私钥
     * @return
     */
    SingleResult<RSAResponse> getRSA();

    /**
     * 获得加解密用的密钥
     * @param request
     * @return
     */
    SingleResult<KeyResponse> getKey(KeyRequest request) throws Exception;
}
@Service
public class EncryptOpenServiceImpl implements EncryptOpenService{
    

    @Value("${rsa.publicKey}")
    private String publicKey;
    @Value("${rsa.privateKey}")
    private String privateKey;
    @Value("${api.encrypt.key}")
    private String key;

    @Override
    public SingleResult<RSAResponse> getRSA() {
        RSAResponse response = RSAResponse.options()
                .setServerPublicKey(publicKey)
                .build();
        return SingleResult.buildSuccess(response);
    }

    @Override
    public SingleResult<KeyResponse> getKey(KeyRequest request)throws Exception {
        String clientPublicKey = RSAUtils.privateDecrypt(request.getClientEncryptPublicKey(), RSAUtils.getPrivateKey(privateKey));
        String encryptKey = RSAUtils.publicEncrypt(key,RSAUtils.getPublicKey(clientPublicKey));
        KeyResponse response = KeyResponse.options()
                .setKey(encryptKey)
                .build();
        return SingleResult.buildSuccess(response);
    }
}
@RestController
@RequestMapping("open/encrypt")
public class EncryptController {
    

    @Autowired
    private EncryptOpenService encryptOpenService;

    @RequestMapping(value = "getRSA",method = RequestMethod.POST)
    //@DisabledEncrypt
    public SingleResult<RSAResponse> getRSA(){
        return encryptOpenService.getRSA();
    }

    @RequestMapping(value = "getKey",method = RequestMethod.POST)
    //@DisabledEncrypt
    public SingleResult<KeyResponse> getKey(@Valid @RequestBody KeyRequest request)throws Exception{
        return encryptOpenService.getKey(request);
    }
}

接口请求的合法性校验

对于一些隐私接口(即必须要登录才能调用的接口),我们需要校验其合法性,即只有登录用户才能成功调用,具体思路如下:
1、调用登录或注册接口成功后,服务端会返回token(设置较短有效时间)和refreshToken(设定较长有效时间)
2、隐私接口每次请求接口在请求头带上token如header(“token”,token),若服务端 返回403错误,则调用refreshToken接口获取新的token重新调用接口,若refreshToken接口继续返回403,则跳转到登录界面。
这种算法较为简单,这里就不写出具体实现了。
由于篇幅问题,剩余方式下篇会继续介绍,敬请期待!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lynnlovemin/article/details/80638765

智能推荐

gradle下载的缓存路径-程序员宅基地

文章浏览阅读368次。为什么80%的码农都做不了架构师?>>> ..._download_jsc.gradle下载路径

如何在iPhone或iPad上安装iOS 11 Beta-程序员宅基地

文章浏览阅读2.6k次。The public beta of iOS 11 is now available for iPhones and iPads. Anyone who wants to play withiOS 11’s new featurescan install it today. However, we recommend backing up your device first so you ca..._ios11bate

UE4_UE5结合offline voice recognition插件做语音识别功能_ue5语音识别-程序员宅基地

文章浏览阅读6.2k次,点赞4次,收藏24次。市面上主流的语音识别大多是用科大讯飞的SDK,但是那个也不是完全免费使用的,于是我选择使用offline voice recognition的语音识别,购买插件终生使用。offline voice recognition插件在UE官方商城卖200多元。我将它需要的资源都打包成一个rar,分享给有需要的人。其中就有两个UE工程,一个是UE4.27版本的,另外一个是UE5的版本。并且也下载了两个中文的语言包,一个是简版,另外一个是完整版,对于 只是做简单的主意指令的只需要用简版的语言包即可,大大提升识别速度。第_ue5语音识别

联想计算机哪款好用,华为和联想电脑哪个好用-程序员宅基地

文章浏览阅读1.8k次。对于一些具有电脑购买需求的朋友来说,在选择购买的时候经常会有一个困惑,那就是华为的电脑和联想的电脑哪一个好用?如果您现在也存在着这样的问题,那么不妨一起来看一下下面对于这两个品牌电脑的相关介绍。联想是比较早生产电脑的一家厂商,本身也是依靠做台式电脑和笔记本电脑起家的,所以也在经过长时间的发展之后一定有成熟的一面,我的一个朋友在多年之前购买了一台联想台式电脑,现在他仍然还在用着这一款台式电脑,由此可..._联想电脑和华为电脑哪个好

为什么搜狗输入法显示服务器异常,为什么搜狗拼音输入法一直出现错误报告?...-程序员宅基地

文章浏览阅读2.6k次。官方论坛有解释和解决办法 对近期输入法弹出str数据错误报告的声明和致歉 尊敬的搜狗拼音用户: 近期我们收到了很多对搜狗拼音输入法3.5奥运版str数据文件错误报告的问题反馈。其某个版本中出现错误后法会弹出类似下面所述的错误报告,影响了您的操作和体验,对此我们非常抱歉。 str数据文件格式错误: C:Documents and SettingsuserApplication DataSogouPY..._sogoupy/sgim_keymap.bin

dreamtalk 学习笔记-程序员宅基地

文章浏览阅读523次,点赞8次,收藏7次。dreamtalk 学习笔记

随便推点

CentOS 7.X 源码编译安装MariaDB-10.2.X_group ‘mail’ not found-程序员宅基地

文章浏览阅读2.5k次。CentOS 7 编译安装MariaDB-10.2.XCentOS 7下mariadb-10.1.22 源码编译安装过程笔记,希望对大家有帮助。 下载文件https://mariadb.com/ 或 https://downloads.mariadb.org/mariadb/10.2.11/
源码包的下载下载链接: https://mirrors.tuna.tsinghua.edu.cn/m_group ‘mail’ not found

wps中的大客户版本_wps 大客户版-程序员宅基地

文章浏览阅读462次,点赞11次,收藏8次。网上的WPS各个版本基本都带有稻香插件,广告一堆堆,用户需要注册才能减少广告的显示,但依然时不时跳出来显示。其实WPS给大学做过一些版本,这种定制版本应用于大学院系的文档编写使用。最关键的是这个版本没有广告,没有稻香,没有推荐模板,只有纯净的WPS。界面虽然不华丽,风格还是很传统office2000风格,不是烦人的多标签页,也不会隐藏。工具栏不会藏着掖着直接横排展示所有图标,鼠标不用切换页,所有工具一目了然。这集美大学版本是办公软件的不二之选。_wps 大客户版

Android自动化页面测速在美团的实践-程序员宅基地

文章浏览阅读587次,点赞8次,收藏19次。我们都知道ViewPager的Tab切换是可以通过一个 OnPageChangeListener 对象进行监听的,所以我们可以为ViewPager添加一个自定义的Listener对象,在切换时记录一个时间,这样可以通过用这个时间减去页面创建后的时间得出这个多余的等待时间,上报时在总时间中减去即可。这里的 getConfigModel() 方法中,会使用页面的类名或者全路径类名,去初始化时解析的配置Map中进行id的匹配,如果匹配到说明页面需要测速,就会创建测速对象 PageObject 进行测速。

百度竞价悄然改版-程序员宅基地

文章浏览阅读42次。前段时间百度因为“魏则西事件”而被要求整改,百度的竞价排名也被推向了风口浪尖。最近有网友@闪电精灵SEO张扬爆料:百度竞价已改版,竞价显示4条,或许取消了右侧排名! 但是在经过风波之后,百度的确有了一些变化,最明显的变化就是对推广的网站的标注,之前的对百度推广只有推广两个字,现在是成了蓝色..._python 百度竞价排名

《剑指offer》学习笔记_面试题35_复杂链表的复制-程序员宅基地

文章浏览阅读334次。题目描述 请实现一个函数,复制一个复杂链表。在复杂链表中,每个节点除了一个next指针指向下一个节点,还有一个random指针指向链表中的任意节点或者为空。 思路 1.分治先复制当前的节点,然后递归的分别复制next或random。在复制next和random的过程中会重复复制相同节点,因此需要一个map来记录当前的复制情况。2.拆分法首先,将复制节点拼接到原始节点的...

嵌入式linux项目介绍与分享-基于 Linux 下 Socket 网络编程的局域网聊天室_linux项目局域网聊天室-程序员宅基地

文章浏览阅读1.4k次,点赞52次,收藏14次。本项目是基于 Linux 下 Socket 网络编程的局域网聊天室,实现了账号注册与登录、私聊消息、群发消息、发送离线消息、查看聊天记录、修改昵称密码等功能,并设置管 理员,实现将用户禁言、解禁、踢出聊天室等,采用多线程并发服务器模型处理多个客户端的同时连接和请求,服务器创建并管理用户数据、在线用户数据、聊天数据、离线消息数据等 SQlite 数据库,并提供后台服务,客户端通过 TCP 协议建立与服务器的稳定连接,并通过格式化输入输出实现与用户的交互。_linux项目局域网聊天室