文字直播用 chatgpt 开发一个笔记软件。 希望可以在睡觉前部署上线。

2023-06-08 20:53:00 +08:00
 hamsterbase

下面是之前的聊天记录

Q: 我现在希望开发一个卡片笔记软件。UI 界面类似于 imessage 。 请问叫什么名字比较好?

A 为了取一个好名字,需要考虑软件的特点和目标用户。考虑到你的软件类似于 iMessage ,那么你可以考虑使用类似的命名方式。以下是一些可能的建议:

CardMessage NoteChat CardNote SnapNote MemoTalk NoteMessage 在考虑具体的名字时,可以考虑软件的品牌形象,功能特点等方面,以便从众多同类软件中脱颖而出

  1. 搜了一下,memo talk 之前没人开发过,就叫这个名字吧。

  2. 建了一个 GitHub 仓库 https://github.com/hamsterbase/memo-talk

Q: sha256 转化为字符串,长度是多少。

A:

SHA256 是一种加密算法,它生成的哈希值( hash value )通常表示为 64 个字符的十六进制字符串( hexadecimal string ),因此 SHA256 转化为字符串后的长度是 64 个字符。每个字符是 0-9 和 A-F 之间的一个 16 进制数字,因此它们可以表示 256 个可能的值( 16 的 4 次方)。

5

Q:

我希望你能帮我编写一下服务端的代码。

需要有以下四个接口接口为

  1. 获取文件夹列表

  2. 根据文件夹名字,文件名获取文件内容

  3. 根据文件夹名字,文件名删除文件

  4. 根据文件夹名字,文件名删除文件

要求。

  1. 基于 koa 编写
  2. 使用 typescript 编写
  3. 文件名,文件名必须为 64 个字符,每个字符是 0-9 和 A-F 之间的一个 16 进制数字
  4. 文件夹名和文件名,文件内容都通过 body 传递。

A: 以下是一个基于 Koa 和 TypeScript 的简单实现,其中包含了四个接口:

import Koa from "koa";
import bodyParser from "koa-bodyparser";

const app = new Koa();

// 定义文件夹列表
let folders: string[] = [];

// 定义文件内容
let files: { [key: string]: string } = {};

// 获取文件夹列表
app.use(async (ctx, next) => {
  if (ctx.path === "/folders" && ctx.method === "GET") {
    ctx.body = folders;
    ctx.status = 200;
  } else {
    await next();
  }
});

// 获取文件内容
app.use(async (ctx, next) => {
  if (ctx.path === "/files" && ctx.method === "GET") {
    const folderName = ctx.request.body.folderName;
    const fileName = ctx.request.body.fileName;
    const filePath = `${folderName}/${fileName}`;
    if (files[filePath]) {
      ctx.body = files[filePath];
      ctx.status = 200;
    } else {
      ctx.body = "File not found";
      ctx.status = 404;
    }
  } else {
    await next();
  }
});

// 添加文件
app.use(bodyParser());
app.use(async (ctx, next) => {
  if (ctx.path === "/files" && ctx.method === "POST") {
    const folderName = ctx.request.body.folderName;
    const fileName = ctx.request.body.fileName;
    const fileContent = ctx.request.body.fileContent;
    const filePath = `${folderName}/${fileName}`;
    if (files[filePath]) {
      ctx.body = "File already exists";
      ctx.status = 409;
    } else {
      files[filePath] = fileContent;
      ctx.status = 201;
    }
  } else {
    await next();
  }
});

// 删除文件
app.use(async (ctx, next) => {
  if (ctx.path === "/files" && ctx.method === "DELETE") {
    const folderName = ctx.request.body.folderName;
    const fileName = ctx.request.body.fileName;
    const filePath = `${folderName}/${fileName}`;
    if (files[filePath]) {
      delete files[filePath];
      ctx.status = 204;
    } else {
      ctx.body = "File not found";
      ctx.status = 404;
    }
  } else {
    await next();
  }
});

app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

请注意,此代码仅供参考,可能需要根据具体需求进行修改和调整。

3978 次点击
所在节点    分享创造
62 条回复
llsongll
2023-06-09 09:44:08 +08:00
感觉不错~
hamsterbase
2023-06-09 10:51:29 +08:00
@findex

建议改一下默认授权。 上来就要求我私有库权限。 我选择拒绝。
hamsterbase
2023-06-09 10:53:08 +08:00
@ShuaiYH

你们默认的权限要的太多了。 建议只授权 public 项目 + 邮箱。 等用户需要部署私有项目的时候,再请求进一步的授权。
之前我就是被这个方案劝退了。
HulkLee
2023-06-09 20:22:08 +08:00
哈哈哈 今天晚上的几点开始,蹲一波
hamsterbase
2023-06-09 20:47:12 +08:00
@HulkLee 开始开始。~~~
hamsterbase
2023-06-09 20:51:20 +08:00
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 , 让软件可离线使用
hamsterbase
2023-06-09 20:57:47 +08:00
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>;
}
hamsterbase
2023-06-09 21:00:22 +08:00
好的,我可以帮您实现一个基于 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 并输出错误信息。
hamsterbase
2023-06-09 21:18:45 +08:00
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>;
}
```
hamsterbase
2023-06-09 21:28:27 +08:00
4. 本来准备在 vite 里跑 mocha ,试了一下发现不太行,准备换一条路。 用假的 indexedDB 测试。 测试继续在 vitest 里跑。
hamsterbase
2023-06-09 21:37:09 +08:00
5. 逻辑和测试都让 chatgpt 写好了, 看了一下测试似乎没问题

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

https://github.com/hamsterbase/memo-talk-web/commit/31cc5a97a31d69f015f6ecfd089fb3147c1db7dd
hamsterbase
2023-06-09 21:57:01 +08:00
6. 使用 vite 的多页面模式开发。 初始化一下 settings 页面。 配置一下 vite 的构建配置


https://github.com/hamsterbase/memo-talk-web/commit/d7024d70c6c63cca47aa550175504aa95e22f3eb
echoless
2023-06-09 21:59:11 +08:00
https://github.com/swuecho/chat

你用我这个 gpt 套🉑️, 可以直接导出 markdown 或者分享对话. 好看多了
hamsterbase
2023-06-09 22:35:31 +08:00
7. 因为是极简的稍后读工具,所以在不校验账户密码。 只需要确保账户密码非空就行了

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

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

https://github.com/hamsterbase/memo-talk-web/commit/c5559845d27cdeba278c9969330c474e26db9ac6
hamsterbase
2023-06-09 23:12:22 +08:00
8. 简单更新了一下首页的 UI , 然后把储存从 localstorage 换到了 IndexedDB
开始真正对接服务器了。

第一版做的简单一点,在页面加载的时候异步合并一下服务器的数据,合并后把数据上传到服务器。
hamsterbase
2023-06-09 23:21:16 +08:00
9 。 开始编写服务器需要的一些工具函数


Q:
帮我用 crypto-js 编写函数。

输入账户,密码。 生成两个 key

第一个 key sha256(memo:sha256(username):sha256(password):talk);
第二 key 。sha256(memo:password:talk

用 ts 编写

第一个 key 名字为 userToken , 第二个 key 名字为 加密密钥(用英文)
hamsterbase
2023-06-09 23:26:37 +08:00
10. 帮我编写一个 aes 加密函数. 输入 key 和 data, 返回加密后的数据. 输入 data 和 key , 返回解密后的数据

基于 crypto-js 和 ts
hamsterbase
2023-06-09 23:28:01 +08:00
11: 帮上面的 encryptedData ,decryptedData 写测试。 随机的 key 和 data 。 加密和解密后数据不变
hamsterbase
2023-06-09 23:29:59 +08:00
12:帮我写一个 sha256 的函数。 基于 ts . crypto-js. 输入字符串,输出 hex
hamsterbase
2023-06-09 23:32:10 +08:00
13: POE 一天一次的 gpt4 体验卡

帮我基于下面的代码,写一个 js sdk 出来。


https://github.com/hamsterbase/memo-talk/blob/main/server/index.ts

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/947094

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX