V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  hamsterbase  ›  全部回复第 6 页 / 共 17 页
回复总数  338
1 ... 2  3  4  5  6  7  8  9  10  11 ... 17  
7. 因为是极简的稍后读工具,所以在不校验账户密码。 只需要确保账户密码非空就行了

简单完成了账户密码的保存功能。 未来会加上警告,用户协议。 目前暂时先不加.

因为 antdmobile 是在 2019 年之后出的,chatgpt 估计不认识,就手写了

https://github.com/hamsterbase/memo-talk-web/commit/c5559845d27cdeba278c9969330c474e26db9ac6
6. 使用 vite 的多页面模式开发。 初始化一下 settings 页面。 配置一下 vite 的构建配置


https://github.com/hamsterbase/memo-talk-web/commit/d7024d70c6c63cca47aa550175504aa95e22f3eb
5. 逻辑和测试都让 chatgpt 写好了, 看了一下测试似乎没问题

他给的是在 IndexedDBSettingService 构造函数里调用 openDB, 我改成了每次 set , get 的时候初始化,似乎没问题。

https://github.com/hamsterbase/memo-talk-web/commit/31cc5a97a31d69f015f6ecfd089fb3147c1db7dd
4. 本来准备在 vite 里跑 mocha ,试了一下发现不太行,准备换一条路。 用假的 indexedDB 测试。 测试继续在 vitest 里跑。
3. 对 chatgpt 写的代码不太放心,准备写一个单元测试

Q: 帮我用 mocha 写单元测试

```ts
export interface ISettingService {
get<V extends SettingsValue>(key: string, defaultValue: V): Promise<V>;

set(key: string, value: SettingsValue): Promise<void>;

readConfig<T extends Record<string, SettingsValue>>(
defaultValue: T
): Promise<T>;
}
```
好的,我可以帮您实现一个基于 IndexedDB 的 ISettingService 。以下是实现代码示例:

typescript
Copy
export interface ConfigDatabase {
settings: Record<string, SettingsValue>;
}

export class IndexedDBSettingService implements ISettingService {
private readonly dbName: string = "my-settings-db";
private readonly dbVersion: number = 1;
private readonly storeName: string = "settings";
private db: IDBDatabase;

constructor() {
this.openDB();
}

private async openDB(): Promise<void> {
return new Promise<void>((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);

request.onerror = (event) => {
console.error("Error opening database", event);
reject(event);
};

request.onsuccess = (event) => {
this.db = request.result;
resolve();
};

request.onupgradeneeded = (event) => {
const db = request.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
});
}

public async get<V extends SettingsValue>(
key: string,
defaultValue: V
): Promise<V> {
return new Promise<V>((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, "readonly");
const objectStore = transaction.objectStore(this.storeName);

const request = objectStore.get(key);

request.onerror = (event) => {
console.error(`Error getting value for key "${key}"`, event);
reject(event);
};

request.onsuccess = (event) => {
const value = request.result !== undefined ? request.result : defaultValue;
resolve(value);
};
});
}

public async set(key: string, value: SettingsValue): Promise<void> {
return new Promise<void>((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, "readwrite");
const objectStore = transaction.objectStore(this.storeName);

const request = objectStore.put(value, key);

request.onerror = (event) => {
console.error(`Error setting value for key "${key}"`, event);
reject(event);
};

request.onsuccess = (event) => {
resolve();
};
});
}

public async readConfig<T extends Record<StorageKeys, SettingsValue>>(
defaultValue: T
): Promise<T> {
const config: T = {} as T;

// Read all values from the database and merge with default values
for (const key in defaultValue) {
if (defaultValue.hasOwnProperty(key)) {
const value = await this.get(key, defaultValue[key]);
config[key as keyof T] = value as T[keyof T];
}
}

return config;
}

public async clearAll(): Promise<void> {
return new Promise<void>((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, "readwrite");
const objectStore = transaction.objectStore(this.storeName);

const request = objectStore.clear();

request.onerror = (event) => {
console.error("Error clearing settings", event);
reject(event);
};

request.onsuccess = (event) => {
resolve();
};
});
}
}
在这个实现中,我们创建了一个名为 my-settings-db 的 IndexedDB 数据库,并创建了一个名为 settings 的对象存储。在 get 和 set 方法中,我们分别使用事务来读取和写入数据。在 readConfig 方法中,我们遍历默认配置对象中的所有键,并使用 get 方法来获取每个键的值。最后,我们还为 IndexedDBSettingService 类添加了一个清除所有设置的 clearAll 方法。

请注意,由于 IndexedDB 是异步的,因此所有方法都返回一个 Promise 。此外,如果 IndexedDB 连接失败或出现错误,这些方法将会拒绝 Promise 并输出错误信息。
2. localstorage 存储量有限,准备基于 IndexedDB 实现本地存储


Q: 帮我基于 IndexedDB 实现一个 ISettingService 吧,我要把所有的配置都存在 IndexedDB 里面。

export type SettingsValue = string | boolean;

export enum StorageKeys {
backend = "backend",
"backend.hamsterbase.entrypoint" = "backend.hamsterbase.entrypoint",
"backend.hamsterbase.token" = "backend.hamsterbase.token",
"backend.notion.token" = "backend.notion.token",
"backend.notion.databaseId" = "backend.notion.databaseId",
"backend.notion.license" = "backend.notion.license",
autoOn = "autoOn",
autoOnBlockList = "autoOnBlockList",
}

export interface ISettingService {
_serviceBrand: undefined;

get<V extends SettingsValue>(key: string, defaultValue: V): Promise<V>;

set(key: string, value: SettingsValue): Promise<void>;

readConfig<T extends Record<StorageKeys, SettingsValue>>(
defaultValue: T
): Promise<T>;
}
1. 今天的目标: 发布 1.0 !!!

1. 接入 antd mobile ,重写 UI
2. 接入后端,实现登录功能.
1. 服务器不储存任何账户密码。只储存用户的 token ,token 由前端生成。
2. 把 token 作为文件夹名
3. token 的生成规则:sha256( `${sha256(user)} ${sha256(password)}`)
4. 文件储存之前使用 aes 加密。 加密 为 sha256(password)
3. 接入 PWA , 让软件可离线使用
@HulkLee 开始开始。~~~
太厉害了。 我也准备用类似方案跑我的项目,在 react native 里跑 nodejs 。实现一套代码支持全部平台
@ShuaiYH

你们默认的权限要的太多了。 建议只授权 public 项目 + 邮箱。 等用户需要部署私有项目的时候,再请求进一步的授权。
之前我就是被这个方案劝退了。
@findex

建议改一下默认授权。 上来就要求我私有库权限。 我选择拒绝。
23: 前端最基础的功能开发玩了,还没接入服务器。


1. 支持新增消息了, 消息更新后会储存到 local storage 里。
2. 刷新页面后支持恢复消息

在线地址: https://memo-talk-web.onrender.com/

明天晚上继续开始直播。

1. 接入服务
2. 支持设置密码和账户
3. 支持下载当前数据库,支持上传数据库。
21 Q: 帮我写一个 react 的页面,需要用 typescript 开发。


最顶上是一个提示框。 红色的字 “此项目还在开发中,请不要使用”

中间为一个消息窗口。 消息都靠右。

消息为圆角。消息的背景为蓝色。 参考 imessage

页面背景为白色。

最下面是一个 input 框。 里面有一个按钮。 按钮上面的字为 “发送”


颜色要好看一点。 圆角保存统一。

props 为 memotalks:MemoTalk, onCreateMemoTalk(content:string):void

export interface MemoTalk {
id: string;
content: string;
createTime: number;
}
20. 完善一下单元测试

Q: 帮我用 jest 编写测试。

如果重复删除,应该抛错
19. 在 chatgpt 的帮助下测出了一个 bug, 如果删除了,再次获取的时候应该返回 null 。 所有测试都通过啦
18 。 帮我为 IMemoTalkCore 编写集成单元测试。 使用 jest


```ts
/**
* MemoTalkCore 核心为一个 Y.Doc 对象
* 保存的时候 encode 为字符串
* 可以合并别的 MemoTalkCore, 入参为字符串
*/
export interface IMemoTalkCore {
createMemoTalk(content: string): string;

getMemoTalkById(id: string): MemoTalk;

getMemoTalkList(): MemoTalk[];

deleteMemoTalkById(id: string): void;

/**
* 把整个数据库序列化为字符串
*/
encode(): string;

merge(data: string): void;
}
```
17. 在 copilot 的帮助下,完成了核心逻辑的实现


const enum YDocKey {
/**
* 避免 id 重复
*/
id = "id",
memoTalks = "memoTalks",
}

export class MemoTalkCore implements IMemoTalkCore {
private ydoc: Y.Doc;
constructor() {
this.ydoc = new Y.Doc();
}

createMemoTalk(content: string): string {
if (typeof content !== "string") {
throw new Error("content must be string");
}
const idMap = this.ydoc.getMap(YDocKey.id);
let id: string = nanoid();
while (idMap.has(id)) {
id = nanoid();
}
const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks);

const memoTalk = this.ydoc.getMap(id);
memoTalk.set("content", content ?? "");
memoTalk.set("createTime", Date.now());
memoTalksArray.push([id]);
return id;
}

getMemoTalkById(id: string): MemoTalk {
const memoTalk = this.ydoc.getMap(id);
return {
id,
content: memoTalk.get("content") as string,
createTime: memoTalk.get("createTime") as number,
};
}

getMemoTalkList(): MemoTalk[] {
const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks);
return memoTalksArray.map((id) => this.getMemoTalkById(id));
}

deleteMemoTalkById(id: string): void {
const memoTalksArray = this.ydoc.getArray<string>(YDocKey.memoTalks);
memoTalksArray.delete(memoTalksArray.toArray().indexOf(id));
}

encode(): string {
return fromUint8Array(Y.encodeStateAsUpdate(this.ydoc));
}

merge(data: string): void {
Y.applyUpdate(this.ydoc, toUint8Array(data));
}
}
16. 设计一下核心的数据结构。 这个是我自己设计的


export interface MemoTalk {
id: string;
content: string;
createTime: number;
}

/**
* MemoTalkCore 核心为一个 Y.Doc 对象
* 保存的时候 encode 为字符串
* 可以合并别的 MemoTalkCore, 入参为字符串
*/
export interface MemoTalkCore {
createMemoTalk(content: string): string;

getMemoTalkById(id: string): MemoTalk;

getMemoTalkList(): MemoTalk[];

deleteMemoTalkById(id: string): void;

/**
* 把整个数据库序列化为字符串
*/
encode(): string;

merge(data: string): void;
}
1 ... 2  3  4  5  6  7  8  9  10  11 ... 17  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5430 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 15ms · UTC 09:07 · PVG 17:07 · LAX 02:07 · JFK 05:07
Developed with CodeLauncher
♥ Do have faith in what you're doing.