IC开发:Microblog 博客
简单记录 IC 开发 Microblog 博客
要求 1. 显示消息的发布者名字 a. 给 Message 增加 author 字段 b. 增加 set_name 和 get_name 公共方法 2. 增加以下新 UI a. 显示目前 follow 的作者列表 b. 在 timeline 中显示消息作者和时间 3. 关注(follow)其它发布者的 canister a. 关注三个以上作者 b. 点击作者名字,会显示对方发布的消息列表
前端页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <title>Microblog</title> <base href="/" /> <link rel="icon" href="favicon.ico" /> <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css"/> </head> <body> <header><h1>Microblog</h1></header> <main> <div> <label for="message">What do you want to post?</label> <textarea id="message"></textarea> <label for="pwd">password</label> <input id="pwd"/><br/> <button id="post">Post</button> <span id="error"></span> <section id="posts"></section> </div> <br/> <div> <label for="name">Get or set name:</label> <input id="name"/> <span id="info"></span><br/> <button id="getName">Get</button> <button id="setName">Set</button> </div> <br/> <div> <section id="follow"></section> </div> </main> </body> </html>
JS:
import { Int } from "@dfinity/candid/lib/cjs/idl"; import { goodbye_backend } from "../../declarations/goodbye_backend"; async function post() { let post_button = document.getElementById("post"); let err = document.getElementById("error"); err.innerText = ""; post_button.ariaDisabled = true; let textarea = document.getElementById("message"); let text = textarea.value; let pwd = document.getElementById("pwd").value; try { await goodbye_backend.post(pwd, text); textarea.value = ""; } catch (error) { console.error(error); err.innerText = "Failed"; } post_button.ariaDisabled = false; } var num_posts = 0; async function load_posts() { let posts_section = document.getElementById("posts"); let posts = await goodbye_backend.posts(1); if (num_posts == posts.length) return; posts_section.replaceChildren([]); num_posts = posts.length; for(var i = num_posts - 1; i >= 0; i --) { let post = document.createElement("p"); post.innerText = posts[i].author + " SAY: " + posts[i].text + " - " + Date((BigInt(posts[i].time) / BigInt(1000000000))).toLocaleString(); posts_section.appendChild(post); } } async function getName() { let get_button = document.getElementById("getName"); get_button.disabled = true; let name = await goodbye_backend.get_name(); document.getElementById("name").value = name; get_button.disabled = false; } async function setName() { let set_button = document.getElementById("setName"); let info = document.getElementById("info"); info.innerText = ""; set_button.disabled = true; let nameIn = document.getElementById("name"); let name = nameIn.value; try { await goodbye_backend.set_name(name); nameIn.value = ""; info.innerText = "Success"; } catch (error) { console.error(error); info.innerText = "Failed"; } set_button.disabled = false; } var num_follows = 0; async function follows() { let follows = await goodbye_backend.follows(); if (num_follows == follows.length) return; num_follows = follows.length; let info = ""; for(var i = 0; i < num_follows; i ++) { let id = follows[i]['uid']; info += "<button class='info_btn' id= " + id + ">" + follows[i]['uname'] + "</button>"; info += "<section id=" + id + "-msg" + "></section></br>"; } document.getElementById("follow").innerHTML = info; for(var i = 0; i < num_follows; i ++) { let id = follows[i]['uid']; document.getElementById(id).addEventListener('click', function handleClick(event) { load_posts2(id); }) } } var num_posts2 = 0; async function load_posts2(id) { alert("Id: " + id + " , Please wait"); let posts_section2 = document.getElementById(id + "-msg"); try { let posts2 = await goodbye_backend.posts2(id, 1); if (num_posts2 == posts2.length) return; posts_section2.replaceChildren([]); num_posts2 = posts2.length; for(var i = num_posts2 - 1; i >= 0; i --) { let post2 = document.createElement("p"); post2.innerText = posts2[i].author + " SAY: " + posts2[i].text + " - " + Date((BigInt(posts2[i].time) / BigInt(1000000000))).toLocaleString(); posts_section2.appendChild(post2); } } catch (error) { console.error(error); } } async function load() { let post_button = document.getElementById("post"); post_button.onclick = post; load_posts(); setInterval(load_posts, 5000); let get_button = document.getElementById("getName"); get_button.onclick = getName; let set_button = document.getElementById("setName"); set_button.onclick = setName; follows(); } window.onload = load;
Motoko:
import List "mo:base/List"; import Iter "mo:base/Iter"; import Principal "mo:base/Principal"; import Time "mo:base/Time"; import Nat "mo:base/Nat"; actor { public type Message = { author: ?Text; text: Text; time: Time.Time; }; public type User = { uid: Principal; uname: ?Text; }; var king: Text = "Linnuo"; public type Microblog = actor { follow: shared(Principal) -> async (); // add关注对象 follows: shared query () -> async [User]; // return关注对象列表 post: shared (Text) -> async (); // 发布新消息 posts: shared (Time.Time) -> async [Message]; // return 发布消息列表 posts2: shared (Text) -> async [Message]; // return 所有关注对象发布的消息 timeline: shared (Time.Time) -> async [Message]; // return 所有关注对象发布的消息 set_name: shared (Text) -> async (); // set 名字 get_name: shared query () -> async ?Text; // get 名字 unfollow: shared () -> async (); // 清空跟随 }; // stable 修饰:升级不清空内存 stable var followed : List.List<User> = List.nil(); public shared func follow(id: Principal) : async (){ let canister : Microblog = actor(Principal.toText(id)); let name: ?Text = await canister.get_name(); let u = { uid = id; uname = name; }; followed := List.push(u, followed); }; public shared query func follows() : async [User] { List.toArray(followed); }; stable var messages : List.List<Message> = List.nil(); // (msg):获取消息属性 public shared (msg) func post(pwd: Text, text: Text) : async (){ // 获取发送者: dfx identity get-principal // assert(Principal.toText(msg.caller) == "vw7ov-537vk-abslh-s2gx7-gw2ff-v4u6y-thlcs-hejxf-hkkc5-bjiq7-pqe"); //消息发送者 assert(pwd == "qwe123"); let m = { author = ?king; text = text; time = Time.now(); }; messages := List.push(m, messages); // 用钱包调用正常返回失败:dfx canister --wallet=$(dfx identity get-wallet) call microblog_backend post "(\"Second post\")" }; public shared func posts(since: Time.Time) : async [Message] { var list : List.List<Message> = List.nil(); for (m in Iter.fromList(messages)) { if (m.time >= since){ list := List.push(m, list); } }; List.toArray(list); }; public shared func posts2(pid: Principal, since: Time.Time) : async [Message] { try { let canister : Microblog = actor(Principal.toText(pid)); await canister.posts(since); } catch (err) { [] } }; public shared func timeline(since: Time.Time) : async [Message] { var all : List.List<Message> = List.nil(); for (user in Iter.fromList(followed)){ let canister : Microblog = actor(Principal.toText(user.uid)); let msgs = await canister.posts(since); for(msg in Iter.fromArray(msgs)){ all := List.push(msg, all); }; }; List.toArray(all); }; public shared func set_name(name: Text) { king := name; }; public shared query func get_name() : async ?Text { return ?king; }; public shared func unfollow() : async (){ followed := List.nil(); }; // 发送消息:id和名称可以呼唤 // dfx canister call rrkah-fqaaa-aaaaa-aaaaq-cai post "(\"First post\")" // 用Id发消息 // dfx canister call microblog_backend post "(\"Second post\")" // 用名称发消息 // dfx canister call rrkah-fqaaa-aaaaa-aaaaq-cai posts "()" // 获取消息列表 // dfx canister call microblog_backend2 follow "(principal \"$(dfx canister id microblog_backend)\")" // 添加关注对象 // dfx canister call microblog_backend2 follows "()" // 获取关注对象列表 // dfx canister call microblog_backend2 timeline "()" // 获取所有关注对象发布的消息 };
代码的实现并不复杂,欢迎童鞋们指导哈
为了避免直接下载项目,这里就不贴 Github 入口了,好好学习,加油