Deprecated: Constant PDO::MYSQL_ATTR_USE_BUFFERED_QUERY is deprecated since 8.5, use Pdo\Mysql::ATTR_USE_BUFFERED_QUERY instead in /www/wwwroot/www.1998123.xyz/var/Typecho/Db/Adapter/Pdo/Mysql.php on line 71
98123_学习与记录

很好!既然你有 Java 背景,我会通过对比的方式帮你快速上手 Go。我会展示一些常见场景的 Java 和 Go 代码对比。

1. Hello World & 基本结构

Java:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Go:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

关键差异:

  • Go 没有类,函数可以独立存在
  • package main 是可执行程序的入口包
  • 不需要分号(可选)
  • 首字母大写表示公开(public),小写表示私有

2. 变量声明与类型

Java:

public class Variables {
    public static void main(String[] args) {
        // 显式类型
        String name = "Alice";
        int age = 25;
        double salary = 50000.50;
        boolean isActive = true;
        
        // 类型推断 (Java 10+)
        var city = "Taipei";
        
        // 常量
        final int MAX_SIZE = 100;
    }
}

Go:

package main

func main() {
    // 显式类型
    var name string = "Alice"
    var age int = 25
    var salary float64 = 50000.50
    var isActive bool = true
    
    // 类型推断
    var city = "Taipei"
    
    // 短声明(最常用)
    country := "Taiwan"
    
    // 常量
    const MaxSize = 100
}

关键差异:

  • Go 的 := 是最常用的声明方式
  • Go 没有隐式类型转换,必须显式转换
  • 类型写在变量名后面

3. 结构体 vs 类

Java:

public class Person {
    private String name;
    private int age;
    
    // 构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // Getter
    public String getName() {
        return name;
    }
    
    // 方法
    public void introduce() {
        System.out.println("I'm " + name + ", " + age + " years old");
    }
}

// 使用
Person p = new Person("Bob", 30);
p.introduce();

Go:

package main

import "fmt"

// 结构体定义
type Person struct {
    Name string  // 大写=公开
    age  int     // 小写=私有
}

// "构造函数"(惯例)
func NewPerson(name string, age int) *Person {
    return &Person{
        Name: name,
        age:  age,
    }
}

// 方法(接收者)
func (p *Person) Introduce() {
    fmt.Printf("I'm %s, %d years old\n", p.Name, p.age)
}

func main() {
    p := NewPerson("Bob", 30)
    p.Introduce()
}

关键差异:

  • Go 没有类,用结构体 + 方法
  • 没有构造函数,通常用 NewXxx 函数
  • 方法通过接收者(receiver)绑定到类型上
  • 没有 this/self,用接收者变量名

4. 接口

Java:

public interface Speaker {
    void speak();
}

public class Dog implements Speaker {
    @Override
    public void speak() {
        System.out.println("Woof!");
    }
}

public class Cat implements Speaker {
    @Override
    public void speak() {
        System.out.println("Meow!");
    }
}

// 使用
Speaker dog = new Dog();
dog.speak();

Go:

package main

import "fmt"

// 接口定义
type Speaker interface {
    Speak()
}

// Dog 类型
type Dog struct{}

func (d Dog) Speak() {
    fmt.Println("Woof!")
}

// Cat 类型
type Cat struct{}

func (c Cat) Speak() {
    fmt.Println("Meow!")
}

func main() {
    var s Speaker
    
    s = Dog{}
    s.Speak()
    
    s = Cat{}
    s.Speak()
}

关键差异:

  • Go 的接口是隐式实现(duck typing)
  • 不需要 implements 关键字
  • 只要实现了接口的所有方法就自动满足接口

5. 错误处理

Java:

public class ErrorHandling {
    public static int divide(int a, int b) throws ArithmeticException {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return a / b;
    }
    
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Go:

package main

import (
    "errors"
    "fmt"
)

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println(result)
}

关键差异:

  • Go 没有异常(exceptions)
  • 错误作为返回值返回(通常是最后一个返回值)
  • 使用 if err != nil 检查错误
  • nil 表示没有错误

6. 集合操作

Java:

import java.util.*;

public class Collections {
    public static void main(String[] args) {
        // List
        List<String> fruits = new ArrayList<>();
        fruits.add("apple");
        fruits.add("banana");
        
        // Map
        Map<String, Integer> ages = new HashMap<>();
        ages.put("Alice", 25);
        ages.put("Bob", 30);
        
        // 遍历
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        
        for (Map.Entry<String, Integer> entry : ages.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

Go:

package main

import "fmt"

func main() {
    // Slice (动态数组)
    fruits := []string{"apple", "banana"}
    fruits = append(fruits, "orange")
    
    // Map
    ages := make(map[string]int)
    ages["Alice"] = 25
    ages["Bob"] = 30
    
    // 或者字面量初始化
    ages2 := map[string]int{
        "Alice": 25,
        "Bob":   30,
    }
    
    // 遍历 slice
    for i, fruit := range fruits {
        fmt.Printf("%d: %s\n", i, fruit)
    }
    
    // 遍历 map
    for name, age := range ages2 {
        fmt.Printf("%s: %d\n", name, age)
    }
}

关键差异:

  • Go 的 slice 类似 ArrayList,但更底层
  • range 用于遍历,返回索引/键和值
  • _ 忽略不需要的返回值

7. 并发

Java:

public class Concurrency {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 1: " + i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 2: " + i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        t1.start();
        t2.start();
        
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Go:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    
    wg.Add(2)
    
    // Goroutine 1
    go func() {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            fmt.Println("Goroutine 1:", i)
            time.Sleep(100 * time.Millisecond)
        }
    }()
    
    // Goroutine 2
    go func() {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            fmt.Println("Goroutine 2:", i)
            time.Sleep(100 * time.Millisecond)
        }
    }()
    
    wg.Wait()
}

关键差异:

  • go 关键字启动 goroutine(轻量级线程)
  • WaitGroup 等待 goroutine 完成
  • Go 的并发模型更简单、更轻量

8. Channel 通信(Go 特色)

Go 的 channel 是线程安全的通信机制,这是 Java 没有的内置特性:

package main

import "fmt"

func main() {
    // 创建 channel
    messages := make(chan string)
    
    // 发送数据到 channel
    go func() {
        messages <- "Hello"
        messages <- "World"
        close(messages)
    }()
    
    // 从 channel 接收数据
    for msg := range messages {
        fmt.Println(msg)
    }
}

快速学习建议

  1. 先掌握基础:变量、控制流、函数、结构体
  2. 理解指针:Go 有指针但比 C 简单,没有指针运算
  3. 接口的隐式实现:这是 Go 最优雅的特性之一
  4. 错误处理模式:习惯 if err != nil
  5. 并发编程:goroutine 和 channel 是 Go 的杀手级特性

推荐资源

系统篇
查看Linux内核版本

uname -r

查看本机绑定的IP地址

ip a

自动配置IPv6在ens3网卡上(网卡名字修改为自己的网卡)

dhclient -6 ens3

测鸡篇
融合怪混合VPS测试脚本(10代目)

curl -L https://gitlab.com/spiritysdx/za/-/raw/main/ecs.sh -o ecs.sh && chmod +x ecs.sh && bash ecs.sh

SuperBench By 秋水逸冰

bash <(wget -qO- https://down.vpsaff.net/linux/speedtest/superbench.sh)

SuperBench 原版

wget -qO- bench.sh | bash

YABS(Yet Another Bench SH)

curl -sL yabs.sh | bash

硬盘性能/通电时间检测(独立服务器)

wget -q https://github.com/Aniverse/A/raw/i/a && bash a

LemonBench

curl -fsL https://ilemonra.in/LemonBenchIntl | bash -s fast

ChatGPT检测

bash <(curl -Ls https://cpp.li/openai)

ChatGPT检测-Lite版

bash <(curl -Ss "https://raw.githubusercontent.com/candyraws/OpenAI-Checker-lite/main/openai_check_lite.sh")

NetFlix解锁检测

wget -O nf https://github.com/sjlleo/netflix-verify/releases/download/2.01/nf_2.01_linux_amd64 && chmod +x nf && clear && ./nf

流媒体解锁测试

bash <(curl -L -s check.unlock.media)

回程网络测试

wget -qO- git.io/besttrace | bash



玩鸡篇
Docker一键安装(官方)

curl -sSL https://get.docker.com/ | sh


NGINX+PHP80+MariaDB10.6-OneInStack

curl -sSL https://get.docker.com/ | shwget -c http://mirrors.linuxeye.com/oneinstack-full.tar.gz && tar xzf oneinstack-full.tar.gz && ./oneinstack/install.sh --nginx_option 1 --php_option 10 --phpcache_option 1 --php_extensions zendguardloader,ioncube,sourceguardian,imagick,fileinfo,imap,ldap,phalcon,yaf,redis,memcached,memcache,mongodb,swoole,xdebug --phpmyadmin  --db_option 5 --dbinstallmethod 1 --dbrootpwd auik2022 --node  --pureftpd  --redis  --memcached  --reboot

FastPanel

wget http://repo.fastpanel.direct/install_fastpanel.sh -O - | bash -



魔法篇
Wulabing-VLESS 2xmode

wget -N --no-check-certificate -q -O install.sh "https://raw.githubusercontent.com/wulabing/Xray_onekey/main/install.sh" && chmod +x install.sh && bash install.sh

233Boy-V2Ray

bash <(curl -s -L https://git.io/v2ray.sh)

X-UI

bash <(curl -Ls https://raw.githubusercontent.com/vaxilu/x-ui/master/install.sh)

BBR大全

wget --no-check-certificate -O tcp.sh https://github.com/cx9208/Linux-NetSpeed/raw/master/tcp.sh && chmod +x tcp.sh && ./tcp.sh


小结

目前还在继续更新,都是我日常用的脚本,分享给大家,后续会添加更多

Spring中的synchronized容易出现不生效的情况,比如:在controller层加事物注解,内部代码块中含synchronized。

正确使用方式应当是:同步代码块在事物之前开启

为什么先调用@Transactional再调用synchronized会出现问题?

此处原文链接:https://blog.csdn.net/Rambo_Yang/article/details/119885524

1)事务开启在同步代码块之前
2)事务是 Spring 的 AOP 开启的,进入方法前,AOP 就开启了事务
3)事务开启以后才进入方法,再进入同步代码块加锁
4)当同步方法执行结束后,释放锁并提交事物(问题就出现在这里:如果在释放锁和提交事物之间有其它的线程请求,那么处理后的数据没有被提交,导致 synchronized 同步不生效的问题)

HttpSession 服务端的技术
服务器会为每一个用户 创建一个独立的HttpSession

HttpSession原理
当用户第一次访问Servlet时,服务器端会给用户创建一个独立的Session
并且生成一个SessionID,这个SessionID在响应浏览器的时候会被装进cookie中,从而被保存到浏览器中
当用户再一次访问Servlet时,请求中会携带着cookie中的SessionID去访问
服务器会根据这个SessionID去查看是否有对应的Session对象
有就拿出来使用;没有就创建一个Session(相当于用户第一次访问)

域的范围:

Context域 > Session域 > Request域
Session域 只要会话不结束就会存在 但是Session有默认的存活时间(30分钟)

  需要注意的是,一个Session的概念需要包括特定的客户端,特定的服务器端以及不中断的操作时间。A用户和C服务器建立连接时所处的Session同B用户和C服务器建立连接时所处的Session是两个不同的Session。

session进行身份验证的原理:

  当客户端第一次访问服务器的时候,此时客户端的请求中不携带任何标识给服务器,所以此时服务器无法找到与之对应的session,所以会新建session对象,当服务器进行响应的时候,服务器会将session标识放到响应头的Set-Cookie中,会以key-value的形式返回给客户端,例:JSESSIONID=7F149950097E7B5B41B390436497CD21;其中JSESSIONID是固定的,而后面的value值对应的则是给该客户端新创建的session的ID,之后浏览器再次进行服务器访问的时候,客户端会将此key-value放到cookie中一并请求服务器,服务器就会根据此ID寻找对应的session对象了;(当浏览器关闭后,会话结束,由于cookie消失所以对应的session对象标识消失,而对应的session依然存在,但已经成为报废数据等待GC回收了)对应session的ID可以利用此方法得到:session.getId();

1.client-server connection

先上一张图,如下

请输入图片描述

client与server建立一个连接,这种连接是底层的
client发送request到server,等待server的answer
server处理request,将处理结果返还给client,这个结果包括status code、其它data

在HTTP/1.1中,在步骤3执行完成后,connection不再被关闭,在connection有效的前提细,后面client不再需要执行步骤1,直接执行步骤2、3就可以。

为了进一步深入,如下图2,图2是我从国外的网上截下来的,建议读者阅读

请输入图片描述

       图2 HttpSession生成后会有个sessionID

Client第一次发送请求,web container生成唯一的session ID(生成session ID的源码,如有兴趣,可以看下tomcat源码, 随机数+时间+jvmid),并将其返回给client(在web container返回给client的response中),web container上的这个HttpSession是临时的。
后面Client在每次发送请求给服务器时,都将session ID发送给web container,这样web container就很容易区分出是哪个client.
Web container使用这个session ID,找到对应的HttpSession,并将此次request与这个HttpSession联系起来。  

HttpSession生命周期:

  1. 什么时候创建HttpSession

1).对于JSP:
  是否浏览器访问服务端的任何一个JSP或Servlet,服务器都会立即创建一个HttpSession对象呢? 不一定。
  ①.若当前的JSP或(Servlet)是客户端访问的当前WEB应用的第一个资源,且JSP的page指定的session属性为false,则服务器就不会为JSP创建一个HttpSession对象;
  ②.若当前JSP不是客户端访问的当前WEB应用的第一个资源,且其他页面已经创建一个HttpSession对象,则服务器也不会为当前JSP创建一个新的HttpSession对象,而会把和当前会话关联的那个HttpSession对象返回给当前的JSP页面。

2).page指令的session="false"到底表示什么意思:当前JSP页面禁用session隐含变量!但可以使用其他的显式的对象

3).对于Servlet而言:
  若Servlet是客户端访问的第一个WEB应用的资源,则只有调用了request.getSession()或request.getSession(true) 才会创建HttpSession对象

4). 在Servlet中如何获取HttpSession对象?
  request.getSession(boolean create):create为false,若没有和当前JSP页面关联的HttpSession对象,则返回null;
  若有返回true create为true一定返回一个HTTPSession对象。若没有和昂前JSP页面关联的HttpSession对象,则服务器创建一个新的HttpSession对象返回,若有,则直接返回关联。
  request.getSession()等同于request.getSession(true)

  1. 什么时候销毁HttpSession对象:

1).直接调用HttpSession的invalidate()方法:使HttpSession失效
2).服务器卸载了当前Web应用。
3).超出HttpSession的过期时间。

设置HttpSession的过期时间:单位为S

session.setMaxInactiveInterval(5);
out.print(session.getMaxInactiveInterval());

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

  由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。

Session具有以下特点:
(1)Session中的数据保存在服务器端;
(2)Session中可以保存任意类型的数据;
(3)Session默认的生命周期是30分钟,可以手动设置更长或更短的时间。

1:session进行身份验证的原理:

当客户端第一次访问服务器的时候,此时客户端的请求中不携带任何标识给服务器,所以此时服务器无法找到与之对应的

session,所以会新建session对象,当服务器进行响应的时候,服务器会将session标识放到响应头的Set-Cookie中,会以

key-value的形式返回给客户端,例:JSESSIONID=7F149950097E7B5B41B390436497CD21;其中JSESSIONID是固定的,

而后面的value值对应的则是给该客户端新创建的session的ID,之后浏览器再次进行服务器访问的时候,客户端会将此key-value

放到cookie中一并请求服务器,服务器就会根据此ID寻找对应的session对象了;(当浏览器关闭后,会话结束,由于cookie消

失所以对应的session对象标识消失,而对应的session依然存在,但已经成为报废数据等待GC回收了)

对应session的ID可以利用此方法得到:session.getId();

获取Bilibili小程序页面路径
首先通过查看小程序更多资料,获得哔哩哔哩小程序的AppID(目前是wx7564fd5313d24844

可以得到是这样的一个路径:

pages/video/video.html?__preload_=16351231085123&__key_=16351231085124&avid=200376800
注意在后续的小程序跳转操作中,需要把其中的.html删除。
三个参数其中前两个容易想到是时间戳(的10倍多一点)。
第三个参数是AV号,但b站目前只有显式的BV号。获取视频的号,在PC端的播放页面打开F12输入aid即可获得。或者通过其他的接口等方法。

跳转Bilibili小程序页面路径

// index.js


Page({

  // 测试跳转小程序
  goBilibili:function() {
    const aid='200376800'
    const timestamp=new Date().getTime()
    const path=`pages/video/video?__preload_=${timestamp*10+3}&__key_=${timestamp*10+4}&avid=${aid}`
    wx.navigateToMiniProgram({
      appId: 'wx7564fd5313d24844',
      path,
      success: res => {
        console.log('跳转成功')
      }
    })
  }
})