一次小的工程实践-看图猜成语
一 背景
偶尔看到其他机器人有很多插件,有个看图猜成语的很有意思。连续一段时间都在处理客户的问题,刚好周五晚上,顺道做个适合当前框架的插件。
看图猜成语是一个适合群聊的游戏,它是通过调用api实现的。这个api会返回一个图片链接和对应的成语, 再没有回答对正确答案之前,会逐渐做出提示。
分析
背景交代完毕。网上找到了一个比较接近的插件, 看了下跟群里用得还不太一样。不过好在是找到了这个聚合的API。 分析一下实现路径:
- 用户发出命令提示,游戏开始, 给出游戏开始提示(比如用户发出看图猜成语,则游戏开始)
- 调用api后,得到答案和图片链接
- 设置一个定时器给出逐步提示
- 如果有人给出了正确答案,终止提示,游戏结束
- 如果没人回答出来,则最多给到3个字的提示
- 2分钟超时后,给出正确答案,提示游戏结束
提示的部分,比如四字成语一帆风顺,第一次提示:?帆??, 第二次提示:一帆??, 第三次提示:一帆?顺。群里的插件其实细节处理更好,比如第一次提示不会带出最关键的字。我们先做个简单提示,随机一个成语长度-1的位置列表,定时器每次启动把对应位置的字暴露出来。
- 处理中文要转为[]rune ,否则长度计算不准。
- 这里有个洗牌算法,是一个保证公平性和效率的算法。
func generateRandomIndexes(s string) []int {
// 将字符串转换为[]rune以正确处理Unicode字符
runes := []rune(s)
length := len(runes)
if length == 0 {
return []int{}
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
indexes := make([]int, length)
for i := range indexes {
indexes[i] = i
}
// Fisher-Yates洗牌
for i := length - 1; i > 0; i-- {
j := r.Intn(i + 1)
indexes[i], indexes[j] = indexes[j], indexes[i]
}
return indexes
}
接下来就是图片处理,不管是wine还是win下跑的机器人要求图片必须是本地的,所以得有一个下载图片到本地的功能。我的server又是在Linux跑的,所以得额外挂一个模块,接收图片地址和指定目录,实现下载图片和写入指定目录。这样发送图片的控制逻辑,依然在server上来完成。
func imageHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "仅支持GET请求", http.StatusMethodNotAllowed)
return
}
imageURL := r.URL.Query().Get("url")
directory := r.URL.Query().Get("directory")
if imageURL == "" || directory == "" {
http.Error(w, "缺少必要参数:url和directory", http.StatusBadRequest)
return
}
if err := os.MkdirAll(directory, 0755); err != nil {
log.Printf("创建目录失败: %v", err)
http.Error(w, "创建目录失败", http.StatusInternalServerError)
return
}
urlParts := strings.Split(imageURL, "/")
fileName := urlParts[len(urlParts)-1]
if fileName == "" {
fileName = "image.jpg"
}
filePath := filepath.Join(directory, fileName)
resp, err := http.Get(imageURL)
if err != nil {
log.Printf("下载图片失败: %v", err)
http.Error(w, "下载图片失败", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Printf("获取图片失败,状态码: %d", resp.StatusCode)
http.Error(w, fmt.Sprintf("获取图片失败,状态码: %d", resp.StatusCode), http.StatusInternalServerError)
return
}
out, err := os.Create(filePath)
if err != nil {
log.Printf("创建文件失败: %v", err)
http.Error(w, "创建文件失败", http.StatusInternalServerError)
return
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
log.Printf("写入文件失败: %v", err)
http.Error(w, "写入文件失败", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "图片成功下载到: %s", filePath)
}
这个是适合在win下跑的,如果是在wine下跑,根据runtime.GOOS==«linux»稍微再对写入的路径做点改动就可以了。
好久没更新博客了,一是一直被客户需求推着跑,再就是确实没太多可发的。这次的小功能,准备休息时才发现到了凌晨一点,第二天起来才补了wine环境下的处理。也不纯粹是出于兴趣,只是很久没有这样的体验了, 记录一下。