@5ime/likeme 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -212
- package/package.json +42 -23
- package/static/js/likeme.js +1 -1
- package/demo/index.html +0 -43
- package/index.js +0 -103
- package/public/index.html +0 -37
- package/vercel.json +0 -14
package/README.md
CHANGED
|
@@ -1,212 +1,211 @@
|
|
|
1
|
-
# likeMe - 一个可爱的互动点赞组件
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
- `
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
1
|
+
# likeMe - 一个可爱的互动点赞组件
|
|
2
|
+
|
|
3
|
+
快速给你的网站加一个「你喜欢我吗 / Do you like me」小组件。服务端可部署在 Vercel,点赞数据存 Vercel Postgres(Neon)。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 🎨 简洁可爱的 UI 设计
|
|
8
|
+
- 🚀 一键部署到 Vercel
|
|
9
|
+
- 💾 数据存储在 Vercel Postgres(Neon)
|
|
10
|
+
- 🔒 防刷机制:同一 IP 每天只能点赞一次
|
|
11
|
+
- ⚡ 自动缓存优化,减少数据库查询
|
|
12
|
+
- 📦 支持 CDN 和本地文件两种使用方式
|
|
13
|
+
- 🎯 支持自定义主题颜色
|
|
14
|
+
|
|
15
|
+
## 一键部署
|
|
16
|
+
|
|
17
|
+
[](https://vercel.com/new/clone?repository-url=https://github.com/5ime/likeMe)
|
|
18
|
+
|
|
19
|
+
1. 点击上方按钮跳转至 **Vercel** 进行部署
|
|
20
|
+
2. 输入一个你喜欢的 Vercel 项目名称并点击 **Create**
|
|
21
|
+
3. 等待部署完成
|
|
22
|
+
4. 点击 **Go to Dashboard** 跳转到控制台
|
|
23
|
+
|
|
24
|
+
## 配置数据库
|
|
25
|
+
|
|
26
|
+
本项目使用 Vercel Postgres(底层是 Neon)。你只需要在 Vercel 给当前项目绑定数据库,并确保环境变量里存在 `POSTGRES_URL`。
|
|
27
|
+
|
|
28
|
+
### 1. 在 Vercel 创建并绑定数据库
|
|
29
|
+
|
|
30
|
+
1. 打开你的 Vercel 项目
|
|
31
|
+
2. 进入 **Storage**
|
|
32
|
+
3. 点击 **Create Database**,选择 **Neon**
|
|
33
|
+
4. 选择要绑定到的项目(通常就是当前项目)并创建
|
|
34
|
+
|
|
35
|
+
### 2. 确认环境变量已注入
|
|
36
|
+
|
|
37
|
+
创建完成后,Vercel 通常会自动把数据库连接信息注入到项目环境变量中。
|
|
38
|
+
|
|
39
|
+
- 进入 **Settings > Environment Variables**
|
|
40
|
+
- 搜索并确认存在:`POSTGRES_URL`
|
|
41
|
+
|
|
42
|
+
如果没有自动注入,请手动添加:
|
|
43
|
+
|
|
44
|
+
1. 进入 **Storage > Postgres 数据库详情页**
|
|
45
|
+
2. 找到连接字符串(Connection String),复制 `POSTGRES_URL` 对应的值
|
|
46
|
+
3. 回到 **Settings > Environment Variables**,新增变量:
|
|
47
|
+
- **Name**:`POSTGRES_URL`
|
|
48
|
+
- **Value**:你复制的连接字符串
|
|
49
|
+
- **Environments**:勾选 `Production / Preview / Development`
|
|
50
|
+
4. 保存后到 **Deployments** 点击 **Redeploy**(或触发一次新的部署),让最新环境变量生效
|
|
51
|
+
|
|
52
|
+
### 3. 如何验证数据库已可用
|
|
53
|
+
|
|
54
|
+
最简单的方式:访问一次接口触发建表,然后再看是否能正常返回。
|
|
55
|
+
|
|
56
|
+
- `GET /healthz`:服务是否存活(返回 `ok`)
|
|
57
|
+
- `GET /info`:会读取数据库并返回当前点赞数
|
|
58
|
+
|
|
59
|
+
如果数据库连不上,你通常会在 Vercel 的 **Logs** 里看到与连接/鉴权相关的报错(例如环境变量缺失、连接串无效等)。
|
|
60
|
+
|
|
61
|
+
> 本项目会在首次请求时自动建表(`like_count` / `like_user`),无需手动建表。
|
|
62
|
+
|
|
63
|
+
## 在你的网站中使用
|
|
64
|
+
|
|
65
|
+
### 方法一:使用 CDN
|
|
66
|
+
|
|
67
|
+
在你的网页中添加以下代码:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<link rel="stylesheet" href="https://unpkg.com/@5ime/likeme@latest/static/css/likeme.css" />
|
|
71
|
+
<script src="https://unpkg.com/@5ime/likeme@latest/static/js/likeme.js"></script>
|
|
72
|
+
|
|
73
|
+
<div id="likeme"></div>
|
|
74
|
+
|
|
75
|
+
<script>
|
|
76
|
+
likeMe({
|
|
77
|
+
el: '#likeme',
|
|
78
|
+
serverURL: 'https://your-domain.vercel.app', // 替换为你的服务端地址,结尾勿带 /
|
|
79
|
+
color: '#ff9797' // 可选:自定义主题颜色
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
也可以用你自己的 Vercel 域名加载静态资源:
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<link rel="stylesheet" href="https://your-domain.vercel.app/static/css/likeme.css" />
|
|
88
|
+
<script src="https://your-domain.vercel.app/static/js/likeme.js"></script>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 方法二:npm 安装
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install @5ime/likeme
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
然后引入:
|
|
98
|
+
|
|
99
|
+
- `node_modules/@5ime/likeme/static/css/likeme.css`
|
|
100
|
+
- `node_modules/@5ime/likeme/static/js/likeme.js`
|
|
101
|
+
|
|
102
|
+
### 配置选项
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
likeMe({
|
|
106
|
+
el: '#likeme', // 必需:容器选择器
|
|
107
|
+
serverURL: 'https://your-domain.vercel.app', // 必需:服务端地址,结尾勿带 /
|
|
108
|
+
color: '#ff9797' // 可选:主题颜色,默认为 #ff9797
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 本地开发
|
|
113
|
+
|
|
114
|
+
环境:Node.js >= 18
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npm install
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
创建 `.env`:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
POSTGRES_URL=postgres://...
|
|
124
|
+
PORT=3000
|
|
125
|
+
ALLOW_ORIGIN=*
|
|
126
|
+
TRUST_PROXY=1
|
|
127
|
+
INFO_CACHE_SECONDS=5
|
|
128
|
+
LIKE_RATE_LIMIT_WINDOW_MS=60000
|
|
129
|
+
LIKE_RATE_LIMIT_MAX=20
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
启动项目:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm start
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- `PORT` 可选,默认 `3000`
|
|
139
|
+
- `ALLOW_ORIGIN` 可选,默认 `*`(如需限制跨域可填你的站点域名)
|
|
140
|
+
- `TRUST_PROXY` 默认 `1`,用于正确识别客户端 IP。
|
|
141
|
+
- `/info` 默认缓存 5 秒;`/like` 默认每 IP 每分钟 20 次。
|
|
142
|
+
|
|
143
|
+
## API 接口
|
|
144
|
+
|
|
145
|
+
### `GET /info`
|
|
146
|
+
|
|
147
|
+
获取当前点赞数。
|
|
148
|
+
|
|
149
|
+
**响应格式:**
|
|
150
|
+
|
|
151
|
+
成功:
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"code": "200",
|
|
155
|
+
"msg": "success",
|
|
156
|
+
"data": {
|
|
157
|
+
"count": 123
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
失败:
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"code": "201",
|
|
166
|
+
"msg": "哎呀,数据偷懒跑掉了"
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**缓存:** 响应头包含 `Cache-Control: public, max-age=5`(默认缓存 5 秒)
|
|
171
|
+
|
|
172
|
+
### `GET /like`
|
|
173
|
+
|
|
174
|
+
提交一次点赞。同一 IP 每天只能点赞一次。
|
|
175
|
+
|
|
176
|
+
**响应格式:**
|
|
177
|
+
|
|
178
|
+
成功(首次点赞):
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"code": "200",
|
|
182
|
+
"msg": "success",
|
|
183
|
+
"data": {
|
|
184
|
+
"count": 124
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
失败(今日已点赞):
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"code": "201",
|
|
193
|
+
"msg": "今天的爱意已经收到啦~"
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
失败(系统错误):
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"code": "201",
|
|
201
|
+
"msg": "你的爱意传递失败~"
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**限流:** 默认每 IP 每分钟最多 20 次请求
|
|
206
|
+
|
|
207
|
+
### `GET /healthz`
|
|
208
|
+
|
|
209
|
+
健康检查接口,用于检测服务是否正常运行。
|
|
210
|
+
|
|
211
|
+
**响应:** 纯文本 `ok`(HTTP 200)
|
package/package.json
CHANGED
|
@@ -1,23 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@5ime/likeme",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Do you like me? -
|
|
5
|
-
"main": "
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@5ime/likeme",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Do you like me? - 一个可爱的互动点赞组件",
|
|
5
|
+
"main": "static/js/likeme.js",
|
|
6
|
+
"unpkg": "static/js/likeme.js",
|
|
7
|
+
"jsdelivr": "static/js/likeme.js",
|
|
8
|
+
"style": "static/css/likeme.css",
|
|
9
|
+
"files": [
|
|
10
|
+
"static",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node index.js",
|
|
15
|
+
"dev": "node index.js",
|
|
16
|
+
"prepare:public": "node scripts/prepare-public.js",
|
|
17
|
+
"prepublishOnly": "node -e \"const fs=require('fs');['static/css/likeme.css','static/js/likeme.js'].forEach(f=>{if(!fs.existsSync(f)){console.error('missing '+f);process.exit(1)}})\""
|
|
18
|
+
},
|
|
19
|
+
"author": "iami233",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"like",
|
|
23
|
+
"widget",
|
|
24
|
+
"embed",
|
|
25
|
+
"vercel"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@vercel/postgres": "^0.10.0",
|
|
29
|
+
"express": "^5.1.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/5ime/likeMe.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/5ime/likeMe",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/5ime/likeMe/issues"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/static/js/likeme.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const likeMe=(()=>{let instance;return options=>instance||(instance=new LikeMeSingleton(options))})();class LikeMeSingleton{constructor({el,serverURL,color='#ff9797'}){Object.assign(this,{el,url:serverURL,color});this.isLiking=false;this.init()}async init(){try{await this.renderUI();this.attachEvents();this.updateCount()}catch(error){console.error('LikeMe init error:',error)}}async renderUI(){try{const
|
|
1
|
+
const likeMe=(()=>{let instance;return options=>instance||(instance=new LikeMeSingleton(options))})();class LikeMeSingleton{constructor({el,serverURL,color='#ff9797'}){Object.assign(this,{el,url:serverURL,color});this.isLiking=false;this.init()}async init(){try{await this.renderUI();this.attachEvents();this.updateCount()}catch(error){console.error('LikeMe init error:',error)}}async renderUI(){try{const element=document.querySelector(this.el);if(element){element.innerHTML='\n <div class="container">\n <div class="likeCard">\n <div class="likeCard-text">\n 少女祈祷中...\n </div>\n <h3>Do You Like Me</h3>\n </div>\n </div>\n';const card=element.querySelector('.likeCard');if(card){card.style.setProperty('background-color',this.color)}}}catch(error){console.error('Render UI error:',error)}}attachEvents(){const card=document.querySelector(`${this.el} .likeCard`);if(card){card.addEventListener('click',()=>this.handleLike())}}async updateCount(){try{const response=await fetch(`${this.url}/info`);const data=await response.json().catch(()=>null);if(response.ok&&data){const count=data.data?.count||0;const textElement=document.querySelector(`${this.el} .likeCard-text`);if(textElement){textElement.textContent=`❤ ${count}`}}}catch(error){console.error('Update count error:',error)}}setUIState(isLoading,text,isSuccess=false){const card=document.querySelector(`${this.el} .likeCard`);const textElement=document.querySelector(`${this.el} .likeCard-text`);if(card&&textElement){card.classList.remove('loading','success');if(isLoading){card.classList.add('loading')}else if(isSuccess){card.classList.add('success')}textElement.textContent=text}}async handleLike(){if(this.isLiking)return;this.isLiking=true;this.setUIState(true,'❤ 爱意传递中...');try{const response=await fetch(`${this.url}/like`);const payload=await response.json().catch(()=>null);if(!payload){this.setUIState(false,'❤ 网络错误');setTimeout(()=>{this.updateCount()},1500);return}const{code,data={},msg}=payload;const count=data.count||0;if(response.ok&&code==='200'){this.setUIState(false,'❤ 传递成功~',true);setTimeout(()=>{this.setUIState(false,`❤ ${count}`)},500);return}this.setUIState(false,msg||'❤ 传递失败~');setTimeout(()=>{this.updateCount()},1500)}catch(error){console.error('Handle like error:',error);this.setUIState(false,'❤ 网络错误');setTimeout(()=>{this.updateCount()},1500)}finally{this.isLiking=false}}}
|
package/demo/index.html
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta name="description" content="Do You Like Me? - 组件演示页面">
|
|
7
|
-
<meta name="keywords" content="点赞,互动,like,web组件,演示">
|
|
8
|
-
<title>Do You Like Me? - 演示页面</title>
|
|
9
|
-
<link rel="stylesheet" href="/static/css/likeme.css">
|
|
10
|
-
<style>
|
|
11
|
-
.container {
|
|
12
|
-
margin: 0 auto;
|
|
13
|
-
max-width: 400px;
|
|
14
|
-
width: 100%;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.demo-info {
|
|
18
|
-
text-align: center;
|
|
19
|
-
margin-bottom: 20px;
|
|
20
|
-
padding: 10px;
|
|
21
|
-
border-radius: 5px;
|
|
22
|
-
}
|
|
23
|
-
</style>
|
|
24
|
-
</head>
|
|
25
|
-
<body>
|
|
26
|
-
<div class="container">
|
|
27
|
-
<div class="demo-info">
|
|
28
|
-
<h2>LikeMe 组件演示</h2>
|
|
29
|
-
<p>这是一个可交互的点赞组件演示页面</p>
|
|
30
|
-
</div>
|
|
31
|
-
<div id="likeme"></div>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<script src="/static/js/likeme.js"></script>
|
|
35
|
-
<script>
|
|
36
|
-
likeMe({
|
|
37
|
-
el: '#likeme',
|
|
38
|
-
serverURL: `${location.protocol}//${location.host}`,
|
|
39
|
-
color: '#ff4e6a'
|
|
40
|
-
});
|
|
41
|
-
</script>
|
|
42
|
-
</body>
|
|
43
|
-
</html>
|
package/index.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const AV = require('leancloud-storage');
|
|
3
|
-
|
|
4
|
-
const app = express();
|
|
5
|
-
const port = process.env.PORT || 80;
|
|
6
|
-
|
|
7
|
-
AV.init({
|
|
8
|
-
appId: process.env.appId,
|
|
9
|
-
appKey: process.env.appKey,
|
|
10
|
-
serverURL: process.env.serverURL
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const OBJECT_ID = process.env.objectId;
|
|
14
|
-
const account = AV.Object.createWithoutData('likeCount', process.env.objectId);
|
|
15
|
-
|
|
16
|
-
async function getCurrentCount() {
|
|
17
|
-
try {
|
|
18
|
-
const result = await new AV.Query('likeCount').get(OBJECT_ID);
|
|
19
|
-
return result.get('count') || 0;
|
|
20
|
-
} catch (error) {
|
|
21
|
-
try {
|
|
22
|
-
const results = await new AV.Query('likeCount').find();
|
|
23
|
-
const record = results.find(r => r.id === OBJECT_ID) || results[0];
|
|
24
|
-
return record?.get('count') || 0;
|
|
25
|
-
} catch (fallbackError) {
|
|
26
|
-
const firstResult = await new AV.Query('likeCount').first();
|
|
27
|
-
return firstResult?.get('count') || 0;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function checkUserExists(ip) {
|
|
33
|
-
try {
|
|
34
|
-
return await new AV.Query('likeUser').equalTo('ip', ip).first();
|
|
35
|
-
} catch (error) {
|
|
36
|
-
if (error.code === 101) return false;
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function saveUserRecord(ip) {
|
|
42
|
-
try {
|
|
43
|
-
const likeUser = new (AV.Object.extend('likeUser'))();
|
|
44
|
-
likeUser.set('ip', ip);
|
|
45
|
-
await likeUser.save();
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.warn('Error: ', error.message);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
app.use((req, res, next) => {
|
|
52
|
-
res.header('Access-Control-Allow-Origin', '*');
|
|
53
|
-
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
54
|
-
req.method === 'OPTIONS' ? res.sendStatus(200) : next();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
app.use('/static', express.static(`${__dirname}/static`));
|
|
58
|
-
app.use('/demo', express.static(`${__dirname}/demo`));
|
|
59
|
-
app.get('/', (req, res) => res.sendFile(`${__dirname}/public/index.html`));
|
|
60
|
-
|
|
61
|
-
app.get('/info', async (req, res) => {
|
|
62
|
-
try {
|
|
63
|
-
const count = await getCurrentCount();
|
|
64
|
-
res.json({ code: '200', msg: 'success', data: { count } });
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error('Error in /info:', error);
|
|
67
|
-
res.json({ code: '201', msg: '哎呀,数据偷懒跑掉了' });
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
app.get('/like', async (req, res) => {
|
|
72
|
-
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || 'unknown';
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const userExists = await checkUserExists(ip);
|
|
76
|
-
if (userExists) {
|
|
77
|
-
return res.json({ code: '201', msg: '今天的爱意已经收到啦~' });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const currentCount = await getCurrentCount();
|
|
81
|
-
const newCount = currentCount + 1;
|
|
82
|
-
|
|
83
|
-
account.set('count', newCount);
|
|
84
|
-
await account.save();
|
|
85
|
-
|
|
86
|
-
await saveUserRecord(ip);
|
|
87
|
-
|
|
88
|
-
res.json({
|
|
89
|
-
code: '200',
|
|
90
|
-
msg: 'success',
|
|
91
|
-
data: { count: newCount }
|
|
92
|
-
});
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error('Error in /like:', error);
|
|
95
|
-
res.json({ code: '201', msg: '你的爱意传递失败~' });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
app.listen(port, () => {
|
|
100
|
-
console.log(`💖 启动成功!端口:${port}`);
|
|
101
|
-
console.log(`✨ 运行环境:${process.env.NODE_ENV || 'development'}`);
|
|
102
|
-
console.log(`🌍 访问地址:http://localhost:${port}`);
|
|
103
|
-
});
|
package/public/index.html
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta name="description" content="Do You Like Me? - 一个简单的点赞互动组件">
|
|
7
|
-
<title>Do You Like Me?</title>
|
|
8
|
-
<link rel="stylesheet" href="/static/css/likeme.css">
|
|
9
|
-
<style>
|
|
10
|
-
body {
|
|
11
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
12
|
-
margin: 0;
|
|
13
|
-
padding: 50px 20px;
|
|
14
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
15
|
-
min-height: 100vh;
|
|
16
|
-
display: flex;
|
|
17
|
-
justify-content: center;
|
|
18
|
-
align-items: center;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.container {
|
|
22
|
-
max-width: 400px;
|
|
23
|
-
width: 100%;
|
|
24
|
-
}
|
|
25
|
-
</style>
|
|
26
|
-
</head>
|
|
27
|
-
<body>
|
|
28
|
-
<div class="container">
|
|
29
|
-
<div class="likeCard">
|
|
30
|
-
<div class="likeCard-text">
|
|
31
|
-
少女祈祷中...
|
|
32
|
-
</div>
|
|
33
|
-
<h3>Do You Like Me</h3>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</body>
|
|
37
|
-
</html>
|