正如一些讨论的,生成图片是比较贵的。 差不多到了 1 元 一张图了。 就 OpenAI 生成了上面 4 张图,费用 0.4 美元。 https://www.isharkfly.com/t/discourse-openai/15940
正如一些讨论的,生成图片是比较贵的。 差不多到了 1 元 一张图了。 就 OpenAI 生成了上面 4 张图,费用 0.4 美元。 https://www.isharkfly.com/t/discourse-openai/15940
虽然 Discourse 的 AI 接口调用是需要比较高的用户权限或者管理员权限。 但是对已经生成的结果,Discourse 是可以保存并且分享的。 例如,我们搜索了一些美食的做法。 在页面的下面有一个分享 AI 对话的按钮。 在随后弹出的界面中,会有一个更新并且拷贝链接的按钮。 单击上述按钮,就会显示为对话已经拷贝到剪切板中了。 直接粘贴发送就可以了。 例如我们针对上面的对话拷贝的链接为:Making Lanzhou Lamian Noodles - AI Conversation - iSharkFly 任何人都可以单击上面的链接打开你和 AI 对话的内容。 总结 感觉上面的这个功能也是挺有意思的一个功能。 同时在页面的右上角显示显示了当前使用的语言模型的版本。 https://www.isharkfly.com/t/discourse-ai/15910
防火墙 防火墙是肯定要装机器上的,并且端口只开放了 443 和 22。 22 的端口还只限制了部分 IP 段的访问,通常只允许给内部网络的 SSH。 Web 服务应该只走 443,80 端口的做好自动重定向到 443。 CloudFlare 可以用一个 CloudFlare 的负载均衡。 好处是,你的 IP 地址被 CloudFlare 给屏蔽了,直接 Ping 你的网站得到的是 CloudFlare 的 IP 地址。 另外,CloudFlare 也能够帮你挡住一些不正常有威胁的访问。 备份恢复 不管再怎么防范,日常备份肯定要做的。 一般来说 1 天一个备份就够了,最差最差的情况也可以让你的程序很快恢复。 备份的时候最好采取异地备份的方式,不要把备份文件放在本地上,要备份到云存储上。 以防止别人拿到了你的服务器控制权后连备份都拿不回来了。 备份上,也需要看看备份文件的大小,通常备份文件的大小是会增加的,也需要注意下,备份了,但是没有成功的情况,上面通常会有时间戳。 流量监控 网站的流量通常都会有一定规律的,如果有条件拿到 Nginx 的 Access 日志,就可以对每天的访问流量进行监控,如果突然出现不正常的流量就要担心了。 因为我们是附件和文本分离的,大的流量其实丢给了 AWS 的 CloudFront,所以对我们来说流量那边就比较好看。 CPU 和内存使用 这个多是因为有植入木马或者别人用你机器挖矿的这种情况。 表现就是 CPU 被长时间占用到 100%,网站访问上能明显感觉到速度下降。 某些 VPS 服务商,对长时间 CPU 使用率超过 100% 可能会给你邮件通知。 登录用户 SSH 的用户需要使用强密码,禁止 root 用户登录系统。 密码隔一段时间换一个,这样通常不会那么容易被攻入系统。 邮件通知 管理员可以设置一些邮件通知,比如发布回复内容的通知等等。 总结 其实上面的一些配置大部分就是为了让管理员能够了解下当前运行状况。 主要这个是在技术层面的,多了解下流量,基本上也就差不多了。 被植入木马,很多时候是因为其他的原因,我们的其他服务器出现过几次这种情况,多是因为 Linux 自带的防火墙都没有开,还别说硬件防火墙了,整个就在裸奔。 当开了 Linux 自带的防火墙后,到目前,被植入挖矿程序的问题,几乎没有出现。 https://www.isharkfly.com/t/discourse/15768/34
因 Verizon 的电话号码死贵也就算了,还信号极差,对于我们这种还要到处走走的人来说,这个漫游费根本就不是人干的事情。 2 个月居然被收了 500 多的漫游费。 Google Voice 年初的时候换了 Google Fi,这感觉一下子又回到了电话费可控的阶段了,告别了天价漫游费。 但又出现了一个问题,因为老的号码挺好的,想一直保留,现在已经用的是新号码了。 需要一个便宜的保留号码方式。 Verizon 默认是收 10 块钱的费用可以保号停服。 但是这个时间长度只有 3 个月,当 3 个月以后就会自动连接上网络,并且 12 个月只有一次机会。 一次是如果在一年内已经停网保号过一次了,那么就不可以再做一次,而且 3 个月后会自动连接网络。 结果这折腾,号码上个月根本就没用,但还是被收了一个月的电话费,简直是个冤大头。 搜来搜去,突然发现为什么不用 Google Voice 呢? Google Voice 能够允许把其他运营商的号码 Port 过来,费用是一次性的 20,每 6 个月只要这个号码能收一次短信就可以了。 简直是满足了当前的所有需求。 Port 到 Google Voice 把号码转到 Google Voice 上面也是有坑的。一定要注意的是: 转号的时间在 1 到 3 天。 没用确定号码转过来之前,不要停止原服务商的服务和套餐。否则你的号码可能就再也找不回来了,不要问我是为什么知道的。 需要获得原服务商的电话号码转移密码和账号信息,这个可以从原服务商网站上找到。 下图显示了提交转号的进程,怎么着看来也得一天。 吐槽下 Verizon 也不知道 Verizon 怎么想的,用户的合法性校验是通过短信或者他们自己的 APP 来做的。 这次遇到的情况就很郁闷,手机已经没有网络了,短信肯定是收不到了,APP 也没有办法获得验证请求。 但是 Google Voice 转号码又需要向被转的号码上发一个验证短信,以确认你是否拥有这个号码。 结果是电话 Verizon 1 个多小时,然后让他们远程给手机上面下发一个 eSim 卡才解决问题。 就在想,难道就不能提供一个通过邮件校验的方式来做吗? 上网考古了下,对 Verizon 的这种奇葩做法,很多人也是骂声一片,就你们做的这个 APP ,什么业务都往上丢,简直害人呀。 为了拿到转号要的 PIN,Verizon 要验证,然后上面的那个号码是数据卡,根本收不到短信。 你这不是欺负人嘛,不知道他们脑袋是不是门挤了。 Port 时间 当邮箱中收到下面的邮件后,就说明你的号码已经 Port 到 Google Voice 了。 电话已经可以使用了,但短信貌似还没有办法使用,显示成功,但是没有收到短信。 等了差不多一个小时就可以收到短消息了。 当号码被 Port 过来后,再登录 Verizon,会发现所有有关号码的内容都会删除了。 如果有自动扣款的设置,记得把这个自动扣款取消掉,因为不知道什么时候又莫名其妙的扣款了。 终于可以告别 Verizon 了。 https://www.isharkfly.com/t/topic/15802
苹果在2022年的WWDC大会上宣布了在iOS设备上可以使用一种名叫Passkey的无密码登录,苹果把它翻译成“通行密钥”,并随后又在macOS上支持它。 谷歌和微软随后也宣布在Android和Windows系统上支持通行密钥。 密码登录,要求用户设置一个高强度的密码,因为强度低,容易被破解,但强度高,又记不住。很多网站要求注册时密码必须包含大小写字母、数字和特殊符号,纯属给用户添堵。 而用Passkey登录,则无需输入密码,只需要点一下“一键登录”,就可以直接完成登录。 这种无密码登录到底是什么原理,为什么这么神奇? 其实Passkey的核心技术非常简单粗暴,就是用非对称密钥通过签名完成认证。 Discourse passkey Discourse 已经能够完全支持 passkey 的登录,并且这一登录方式作为默认选项已经放在了用户登录选项中。 在注册的时候,还是可以按照正常注册。设置部分是在注册并且激活成功后。 设置 passkey 设置 passkey 的部分是在用户的安全属性哪里。 进入你的用户选项,然后选择添加 Passkey。 如果使用的是 Windows 计算机的话,上面可以选择使用你的计算机 PIN。 我们这里选择使用其他设备,因为想使用手机的照相功能。 在随后的界面中,单击下一步继续。 随后,屏幕上会出现一个 QR 二维码,使用手机的照相机功能进行扫描。 微信的 QR 条码扫描功能无效。 手机上会显示是否保存当前网站的 Passkey,同时是 iPhone 的话还需要校验你的 FaceID。 当单击同意保存后,网站界面将会显示 Passkey 已保存。 单击 OK 继续。 Discourse 会提示要求为使用的 Passkey 创建一个名称。 可以使用默认名称,在这里,我们用名称 iPhone 来表示这个 Passkey 被 iPhone 使用。 然后单击继续。 登录 在登录的时候选择使用 Passkey 登录选项。 然后单击下一步获取 QR 二维码。 使用手机上面的照相功能,在照相机的下方,会显示是否使用 PassKey 进行登录。 按照手机上面的提示,选择要登录的账号,如果在某一个网站上,你有多个账号的话,会提示你选择那个账号进行登录。 然后下一步就需要校验你的 FaceId,就可以完成登录了。 总结 有点像微信的扫描登录的意思。 在微信扫描登录之前需要的是绑定微信号,然后进行扫码登录。 在这里需要的是先注册 Passkey 然后进行扫码登录。 从用户体验上来说和微信登录流程差不多,但微信扫码登录扫的是微信服务器提供的 QR 代码,然后通过微信回调的方式完成用户验证。 换句话说,只要用户微信是登录的,完成扫码就可以登录。 Passkey 的登录方式是哪怕用户扫码成功了,手机上面还是要进行 FaceId 验证,如果 FaceID 验证不通过,还是没有办法登录网站,感觉安全性更高。 另外,PassKey 是通过公钥私钥对的方式进行的,微信登录是通过手机访问微信 QR 方式登录,不存在生物识别二次校验。 从使用场景来说,Passkey 更加广泛,不依赖微信公众号的开发,网站不需要申请微信公众号的接口访问权限,加密方式对比微信来说也更加灵活。 但对用户体验来说,都差不太多。 https://www.isharkfly.com/t/discourse-passkey/15792
最近有一个使用 SSO 把已有的用户同步到 Discourse 的需求。 所以,我就根据官方针对 PHP 实现(Sync DiscourseConnect user data with the sync_sso route - developers - Discourse Meta )写了一个有关 Java 的实现。 实现的方法很简单,但是需要用到一些额外的包,最重要的是 Apache commons codec 和 Okhttp。 Apache commons codec 是用来生成数字签名的,Okhttp 是用来发送 Http Post 请求的。 设置相关的参数 这里有 4 个参数需要提前获得。 获得下面 4 个参数的方法请参考文章:Discourse 使用 DiscourseConnect 来进行用户数据同步 const apiKey = '4fe83002bb5fba8c9a61a65e5b4b0a3cf8233b0e4ccafc85ebd6607abab4651a'; const apiUser = 'system'; const connectSecret = 'jdhb19*Xh3!nu(#k'; 设置 SSO 参数 参数的设置参考了 URL Get 方法的参数设置。 我们 Java 的代码为: URIBuilder builder = new URIBuilder(); builder.addParameter("external_id", "1"); builder.addParameter("email", "info@isharkfly.com"); builder.addParameter("username", "info.visafn.sso"); builder.addParameter("add_groups", "bar"); builder.addParameter("require_activation", "false"); url = StringUtils.removeStart(builder.build().toString(),"?"); System.out.println(StringUtils.removeStart(url, "?")); Base64 和数字签名 当拿到上面 URL 的字符串后,我们有后面 2 个步骤要做。 第一个步骤就是对拿到的 URL 进行 Base64 转换, 第二个步骤就是对拿到已经转换成功的 Base64 字符串进行数字签名,签名这里用了 HMAC_SHA_256 算法。 同时在第一步获得密钥,需要作为参数结合算法参与运算。 String sso= "admin/users/sync_sso"; String sig= new HmacUtils(HmacAlgorithms.HMAC_SHA_256, "55619458534897").hmacHex(ssoPayload); 当拿到上面 2 个值后,重新构建一个 Json 数据结构。 类似下面的数据结构,然后作为 Post 参数的数据: { "sso": "P2V4dGVybmFsX2lkPTEmZW1haWw9aW5mbyU0MHZpc2Fmbi5jb20mdXNlcm5hbWU9aW5mby52aXNhZm4mcmVxdWlyZV9hY3RpdmF0aW9uPXRydWU=", "sig": "403a205a004e37ffab2bf77cc12b2eac352d71820983706d86984eec9821a0c4" } 发送 POST 请求 可以使用任何工具,只要支持 HTTP 的都可以。 现在 Java 用 OkHttp 比较多,所以我们就用 OkHttp 来发送请求。 private OkHttpClient client = new OkHttpClient(); RequestBody body = RequestBody.create( MediaType.parse("application/json"), objectMapper.writeValueAsString(syncSSO)); Response response = client.newCall(postRequest(path, body)).execute(); 上面的 postRequest 是一个方法,在这个方法中我们利用已有的参数来构造请求。 方法是这样写的: public Request postRequest(String path, RequestBody body) { HttpUrl.Builder urlBuilder = HttpUrl.parse(site_url + path).newBuilder(); Request request = new Request.Builder().url(urlBuilder.build().toString()) .addHeader("api-username", api_username) .addHeader("api-key", api_key) .post(body) .build(); return request; } 如果没有问题,上面的代码就能完成 SSO 数据的同步调用。 https://www.isharkfly.com/t/sync-sso-discourseconnect-java/15790
discourse discover 应该允许你把你的 Discourse 实例添加到 Discourse 的 https://discover.discourse.org/ 1 页面中。 直接在你网站的配置上搜索 Discourse Discover ,余下的工作就可以交给 Discourse 了。 还没有选的,可以马上选上喔。 但显然排序肯定不会在第一页。 https://www.isharkfly.com/t/discourse-discover-discourse/15788
在 DiscourseConnect 中,对数据的签名使用的是 HMAC 算法。 实际使用的算法为 HmacSHA256。 Java 生成签名的方法很简单。 String hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, "55619458534897682511405307018226").hmacHex(ssoPayload); HmacUtils 工具类是 Apache Commons Codec 提供的。 Python 和 其他语言都应该有类似的实现。 https://www.isharkfly.com/t/discourse-hmac-java/15789
最近在调试 Java 代码的时候调用上面的 SSO 同步接口,Discourse 返回的错误信息为: {"failed":"FAILED","message":"The `external_id` is required but was blank"} 但是,在我们的代码中是设了值的。 URIBuilder builder = new URIBuilder(); builder.addParameter("external_id", "1"); builder.addParameter("email", "info@isharkfly.com"); builder.addParameter("username", "info.visafn.sso"); // builder.addParameter("add_groups", "bar"); builder.addParameter("require_activation", "false"); url = builder.build().toString(); 不知道为什么会出现上面的错误提示信息。 问题原因 上面的问题原因在于 URL Get 方法参数拼接时候的问题。 如果我们输出 url 字符串: ?external_id=1&email=info%40isharkfly.com&username=info.visafn.sso&require_activation=false Java 的 URL builder 代码会在前面添加一个 ? 号。 但是这个问好对 admin/users/sync_sso 的调用是不能接受的。 所以,为了解决上面的问题,我们需要调用一个 Java 的 API,把上面的问号从字符串中删除。 使用下面的一句话来替换掉上面的 url 直接 toString url = StringUtils.removeStart(builder.build().toString(),"?"); 随后直接运行测试代码,程序应该会返回 200 代码。 https://www.isharkfly.com/t/discourse-discourseconnect-admin-users-sync-sso-422/15787
在对用户数据通过 SSO 同步的时候,调用提示 404 错误。 我们使用的是 Java 的代码。 如上图,返回显示的代码为 404。 问题原因 出现上面错误的原因是安装的 Discourse 实例的 discourse connect 没有启用。 当这个选项不启用的话,API 调用的同步就会显示 404 没有找到。 启用后的就会显示其他的信息。 下图是当上面的接口调用成功后显示的 200 代码。 API 也会返回一个完整的用户相关数据。 数据结构是 JSON 的。 其实内容还蛮多的,你可以用这个把数据同步回 SSO 单点登录服务器上。 { "id": 17, "username": "info.visafn.sso", "name": "Info Visafn Sso", "avatar_template": "/letter_avatar_proxy/v4/letter/i/7ba0ec/{size}.png", "active": true, "admin": false, "moderator": false, "last_seen_at": "2024-05-22T20:14:45.926Z", "last_emailed_at": "2024-05-22T20:32:54.369Z", "created_at": "2024-05-21T18:53:53.081Z", "last_seen_age": 87821.707032433, "last_emailed_age": 86733.264398046, "created_at_age": 179074.552034788, "trust_level": 1, "manual_locked_trust_level": null, "title": null, "time_read": 0, "staged": false, "days_visited": 1, "posts_read_count": 0, "topics_entered": 0, "post_count": 0, "can_send_activation_email": true, "can_activate": false, "can_deactivate": true, "ip_address": null, "registration_ip_address": null, "can_grant_admin": true, "can_revoke_admin": false, "can_grant_moderation": true, "can_revoke_moderation": false, "can_impersonate": true, "like_count": 0, "like_given_count": 0, "topic_count": 0, "post_edits_count": null, "flags_given_count": 0, "flags_received_count": 0, "private_topics_count": 1, "can_delete_all_posts": true, "can_be_deleted": true, "can_be_anonymized": true, "can_be_merged": true, "full_suspend_reason": null, "silence_reason": null, "penalty_counts": { "silenced": 0, "suspended": 0 }, "next_penalty": "2024-05-24T20:38:27.655Z", "primary_group_id": null, "badge_count": 1, "warnings_received_count": 0, "bounce_score": 0, "reset_bounce_score_after": null, "can_view_action_logs": true, "can_disable_second_factor": true, "can_delete_sso_record": true, "api_key_count": 0, "external_ids": {}, "single_sign_on_record": { "user_id": 17, "external_id": "1", "created_at": "2024-05-21T18:53:53.280Z", "updated_at": "2024-05-21T18:56:21.866Z", "external_username": "info.visafn.sso", "external_name": null, "external_avatar_url": null, "external_profile_background_url": null, "external_card_background_url": null }, "approved_by": null, "suspended_by": null, "silenced_by": null, "groups": [ { "id": 10, "automatic": true, "name": "trust_level_0", "display_name": "trust_level_0", "user_count": 14, "mentionable_level": 0, "messageable_level": 0, "visibility_level": 1, "primary_group": false, "title": null, "grant_trust_level": null, "incoming_email": null, "has_messages": false, "flair_url": null, "flair_bg_color": null, "flair_color": null, "bio_raw": null, "bio_cooked": null, "bio_excerpt": null, "public_admission": false, "public_exit": false, "allow_membership_requests": false, "full_name": null, "default_notification_level": 3, "membership_request_template": null, "members_visibility_level": 0, "can_see_members": true, "can_admin_group": true, "publish_read_state": false }, { "id": 11, "automatic": true, "name": "trust_level_1", "display_name": "trust_level_1", "user_count":…