diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e8d0e66
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,85 @@
+# 环境配置
+.env.local
+.env.*.local
+
+# 密钥和证书
+secrets/
+*.key
+*.pem
+*.p12
+*.mobileprovision
+
+# 依赖
+node_modules/
+mini/node_modules/
+
+# 构建输出
+dist/
+build/
+*.min.js
+*.min.wxss
+
+# 小程序 CI
+ci-artifacts/
+mini/ci-artifacts/
+
+# 编辑器
+.idea/
+.vscode/
+*.swp
+*.swo
+*~
+
+# 系统文件
+.DS_Store
+Thumbs.db
+
+# 日志
+*.log
+logs/
+
+# 临时文件
+tmp/
+temp/
+
+# Go
+server/bin/
+server/tmp/
+server/vendor/
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+*.test
+*.out
+
+# 前端构建(如果 server 包含前端)
+server/public/build/
+server/public/hot
+
+# 数据库
+*.db
+*.sqlite
+*.sqlite3
+
+# 上传文件
+uploads/
+storage/uploads/
+
+# 备份
+*.bak
+*.backup
+
+# 压缩包
+*.zip
+*.tar.gz
+*.rar
+
+# 微信开发者工具
+.idea/
+*.sublime-*
+
+# 本地开发
+local/
+.local/
diff --git a/mini/app.json b/mini/app.json
index 7972d50..43ca73c 100644
--- a/mini/app.json
+++ b/mini/app.json
@@ -7,7 +7,9 @@
"pages/fortune/fortune",
"pages/divination/divination",
"pages/solar-terms/solar-terms",
- "pages/settings/settings"
+ "pages/settings/settings",
+ "pages/wish-tree/wish-tree",
+ "pages/wish-detail/wish-detail"
],
"window": {
"backgroundTextStyle": "light",
@@ -46,6 +48,12 @@
"iconPath": "images/fortune.png",
"selectedIconPath": "images/fortune-active.png"
},
+ {
+ "pagePath": "pages/wish-tree/wish-tree",
+ "text": "许愿",
+ "iconPath": "images/wish.png",
+ "selectedIconPath": "images/wish-active.png"
+ },
{
"pagePath": "pages/settings/settings",
"text": "设置",
diff --git a/mini/images/wish-active.png b/mini/images/wish-active.png
new file mode 100644
index 0000000..91a99b9
Binary files /dev/null and b/mini/images/wish-active.png differ
diff --git a/mini/images/wish.png b/mini/images/wish.png
new file mode 100644
index 0000000..91a99b9
Binary files /dev/null and b/mini/images/wish.png differ
diff --git a/mini/pages/wish-detail/wish-detail.js b/mini/pages/wish-detail/wish-detail.js
new file mode 100644
index 0000000..deda90f
--- /dev/null
+++ b/mini/pages/wish-detail/wish-detail.js
@@ -0,0 +1,56 @@
+const { request } = require('../../utils/request.js');
+
+Page({
+ data: {
+ wish: null,
+ loading: true,
+ },
+
+ onLoad(options) {
+ const { id } = options;
+ if (id) {
+ this.loadWishDetail(id);
+ }
+ },
+
+ async loadWishDetail(id) {
+ try {
+ const res = await request({
+ url: `/api/wish/detail/${id}`,
+ method: 'GET',
+ });
+
+ if (res.code === 0) {
+ this.setData({
+ wish: res.data,
+ loading: false,
+ });
+ }
+ } catch (err) {
+ console.error('加载许愿详情失败:', err);
+ // 使用模拟数据
+ this.setData({
+ wish: {
+ id: id,
+ content: '愿家人平安健康,事业顺利,心想事成!',
+ type: 'paid',
+ createdAt: '2024-01-15 12:00:00',
+ user: {
+ nickname: '祈福用户',
+ avatar: '',
+ },
+ },
+ loading: false,
+ });
+ }
+ },
+
+ // 分享
+ onShareAppMessage() {
+ const { wish } = this.data;
+ return {
+ title: wish ? `我的愿望:${wish.content.substring(0, 20)}...` : '快来许愿吧!',
+ path: `/pages/wish-detail/wish-detail?id=${wish ? wish.id : ''}`,
+ };
+ },
+});
diff --git a/mini/pages/wish-detail/wish-detail.json b/mini/pages/wish-detail/wish-detail.json
new file mode 100644
index 0000000..280a601
--- /dev/null
+++ b/mini/pages/wish-detail/wish-detail.json
@@ -0,0 +1,4 @@
+{
+ "navigationBarTitleText": "许愿详情",
+ "enablePullDownRefresh": false
+}
diff --git a/mini/pages/wish-detail/wish-detail.wxml b/mini/pages/wish-detail/wish-detail.wxml
new file mode 100644
index 0000000..850ff19
--- /dev/null
+++ b/mini/pages/wish-detail/wish-detail.wxml
@@ -0,0 +1,46 @@
+
+
+ 加载中...
+
+
+
+
+
+
+
+ {{wish.content}}
+
+
+
+
+
+
+
+ 更多愿望
+
+
+ 愿所有美好如期而至
+
+
+
+
+
diff --git a/mini/pages/wish-detail/wish-detail.wxss b/mini/pages/wish-detail/wish-detail.wxss
new file mode 100644
index 0000000..9278a1c
--- /dev/null
+++ b/mini/pages/wish-detail/wish-detail.wxss
@@ -0,0 +1,145 @@
+.container {
+ min-height: 100vh;
+ background: #FFFBF5;
+ padding: 30rpx;
+}
+
+.loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 400rpx;
+ color: #999;
+}
+
+.wish-card {
+ background: #fff;
+ border-radius: 24rpx;
+ padding: 40rpx;
+ box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
+}
+
+.wish-card.paid {
+ border: 2rpx solid #FFD700;
+}
+
+.wish-header {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+ margin-bottom: 30rpx;
+}
+
+.avatar {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ background: #f0f0f0;
+}
+
+.user-info {
+ flex: 1;
+}
+
+.nickname {
+ display: block;
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+}
+
+.time {
+ display: block;
+ font-size: 22rpx;
+ color: #999;
+ margin-top: 8rpx;
+}
+
+.wish-badge {
+ background: linear-gradient(135deg, #FFD700, #FFA500);
+ color: #fff;
+ font-size: 20rpx;
+ padding: 8rpx 16rpx;
+ border-radius: 20rpx;
+}
+
+.wish-content {
+ padding: 30rpx;
+ background: #FFF9F0;
+ border-radius: 16rpx;
+ margin-bottom: 30rpx;
+}
+
+.wish-content text {
+ font-size: 32rpx;
+ color: #333;
+ line-height: 1.8;
+}
+
+.wish-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.type-tag {
+ font-size: 22rpx;
+ padding: 8rpx 16rpx;
+ border-radius: 20rpx;
+}
+
+.type-tag.free {
+ background: #FFE4E1;
+ color: #FF6B6B;
+}
+
+.type-tag.paid {
+ background: #FFF8DC;
+ color: #DAA520;
+}
+
+.btn-action {
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+ background: #C41E3A;
+ color: #fff;
+ font-size: 24rpx;
+ padding: 12rpx 24rpx;
+ border-radius: 30rpx;
+ border: none;
+}
+
+.btn-action .icon {
+ font-size: 28rpx;
+}
+
+/* 推荐区域 */
+.recommend-section {
+ margin-top: 40rpx;
+}
+
+.section-title {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 20rpx;
+}
+
+.recommend-list {
+ display: flex;
+ flex-direction: column;
+ gap: 16rpx;
+}
+
+.recommend-item {
+ padding: 24rpx;
+ background: #fff;
+ border-radius: 16rpx;
+ box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
+}
+
+.recommend-text {
+ font-size: 26rpx;
+ color: #666;
+}
diff --git a/mini/pages/wish-tree/wish-tree.js b/mini/pages/wish-tree/wish-tree.js
new file mode 100644
index 0000000..8c74ae1
--- /dev/null
+++ b/mini/pages/wish-tree/wish-tree.js
@@ -0,0 +1,274 @@
+const { request } = require('../../utils/request.js');
+
+Page({
+ data: {
+ tree: null,
+ wishes: [],
+ loading: true,
+ canvasWidth: 375,
+ canvasHeight: 600,
+ showCreateModal: false,
+ wishContent: '',
+ wishType: 'free', // free: 免费, paid: 付费
+ maxFreeLength: 20,
+ maxPaidLength: 100,
+ products: [],
+ selectedProduct: null,
+ },
+
+ onLoad() {
+ this.loadWishTree();
+ this.loadProducts();
+ },
+
+ onPullDownRefresh() {
+ this.loadWishTree().then(() => {
+ wx.stopPullDownRefresh();
+ });
+ },
+
+ // 加载许愿树数据
+ async loadWishTree() {
+ try {
+ const res = await request({
+ url: '/api/wish/tree',
+ method: 'GET',
+ });
+
+ if (res.code === 0) {
+ this.setData({
+ tree: res.data.tree,
+ wishes: res.data.wishes || [],
+ loading: false,
+ });
+ this.drawTree();
+ }
+ } catch (err) {
+ console.error('加载许愿树失败:', err);
+ this.setData({ loading: false });
+ // 使用本地模拟数据
+ this.setData({
+ tree: { id: 1, name: '祈福许愿树', maxWishes: 100 },
+ wishes: this.getMockWishes(),
+ loading: false,
+ });
+ this.drawTree();
+ }
+ },
+
+ // 加载许愿商品
+ async loadProducts() {
+ try {
+ const res = await request({
+ url: '/api/wish/products',
+ method: 'GET',
+ });
+
+ if (res.code === 0) {
+ this.setData({ products: res.data.list || [] });
+ }
+ } catch (err) {
+ console.error('加载商品失败:', err);
+ // 使用默认商品
+ this.setData({
+ products: [
+ { id: 1, name: '普通许愿条', price: 100, duration: 7, position: 0 },
+ { id: 2, name: '精品许愿条', price: 500, duration: 30, position: 10 },
+ { id: 3, name: '至尊许愿条', price: 2000, duration: 90, position: 100 },
+ ],
+ });
+ }
+ },
+
+ // 模拟许愿数据
+ getMockWishes() {
+ return [
+ { id: 1, content: '愿家人平安健康', type: 'paid', position: 10, isRobot: false },
+ { id: 2, content: '事业顺利', type: 'free', position: 5, isRobot: false },
+ { id: 3, content: '心想事成', type: 'paid', position: 20, isRobot: true },
+ { id: 4, content: '考试通过', type: 'free', position: 3, isRobot: false },
+ { id: 5, content: '财源广进', type: 'paid', position: 15, isRobot: false },
+ ];
+ },
+
+ // 绘制许愿树
+ drawTree() {
+ const ctx = wx.createCanvasContext('wishTree', this);
+ const { canvasWidth, canvasHeight, wishes } = this.data;
+
+ // 清空画布
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+
+ // 绘制树干
+ ctx.setFillStyle('#8B4513');
+ ctx.fillRect(canvasWidth / 2 - 20, canvasHeight - 150, 40, 150);
+
+ // 绘制树冠(圆形)
+ ctx.setFillStyle('#228B22');
+ ctx.beginPath();
+ ctx.arc(canvasWidth / 2, canvasHeight - 200, 120, 0, 2 * Math.PI);
+ ctx.fill();
+
+ // 绘制许愿条
+ wishes.forEach((wish, index) => {
+ const angle = (index / wishes.length) * 2 * Math.PI;
+ const radius = 80 + Math.random() * 40;
+ const x = canvasWidth / 2 + radius * Math.cos(angle);
+ const y = canvasHeight - 200 + radius * Math.sin(angle);
+
+ // 根据类型设置颜色
+ if (wish.type === 'paid') {
+ ctx.setFillStyle('#FFD700'); // 金色 - 付费
+ } else {
+ ctx.setFillStyle('#FF6B6B'); // 红色 - 免费
+ }
+
+ // 绘制许愿条(小矩形)
+ ctx.fillRect(x - 15, y - 5, 30, 10);
+
+ // 绘制文字
+ ctx.setFillStyle('#FFFFFF');
+ ctx.setFontSize(8);
+ ctx.setTextAlign('center');
+ ctx.fillText(wish.content.substring(0, 4), x, y + 3);
+ });
+
+ ctx.draw();
+ },
+
+ // 打开许愿弹窗
+ openCreateModal() {
+ this.setData({ showCreateModal: true });
+ },
+
+ // 关闭许愿弹窗
+ closeCreateModal() {
+ this.setData({
+ showCreateModal: false,
+ wishContent: '',
+ wishType: 'free',
+ selectedProduct: null,
+ });
+ },
+
+ // 输入许愿内容
+ onInputContent(e) {
+ this.setData({ wishContent: e.detail.value });
+ },
+
+ // 选择许愿类型
+ selectType(e) {
+ const type = e.currentTarget.dataset.type;
+ this.setData({ wishType: type });
+ },
+
+ // 选择商品
+ selectProduct(e) {
+ const id = e.currentTarget.dataset.id;
+ const product = this.data.products.find(p => p.id === id);
+ this.setData({ selectedProduct: product });
+ },
+
+ // 提交许愿
+ async submitWish() {
+ const { wishContent, wishType, selectedProduct } = this.data;
+
+ if (!wishContent.trim()) {
+ wx.showToast({ title: '请输入许愿内容', icon: 'none' });
+ return;
+ }
+
+ // 检查字数限制
+ const maxLength = wishType === 'free' ? this.data.maxFreeLength : this.data.maxPaidLength;
+ if (wishContent.length > maxLength) {
+ wx.showToast({ title: `最多输入${maxLength}个字`, icon: 'none' });
+ return;
+ }
+
+ // 付费许愿需要选择商品
+ if (wishType === 'paid' && !selectedProduct) {
+ wx.showToast({ title: '请选择许愿商品', icon: 'none' });
+ return;
+ }
+
+ try {
+ // 如果是付费许愿,先创建订单
+ if (wishType === 'paid') {
+ const orderRes = await request({
+ url: '/api/pay/create',
+ method: 'POST',
+ data: {
+ type: 'wish',
+ productId: selectedProduct.id,
+ content: wishContent,
+ },
+ });
+
+ if (orderRes.code === 0) {
+ // 调起微信支付
+ await this.wxPay(orderRes.data);
+ }
+ } else {
+ // 免费许愿直接创建
+ const res = await request({
+ url: '/api/wish/create',
+ method: 'POST',
+ data: {
+ content: wishContent,
+ type: 'free',
+ treeId: this.data.tree.id,
+ },
+ });
+
+ if (res.code === 0) {
+ wx.showToast({ title: '许愿成功', icon: 'success' });
+ this.closeCreateModal();
+ this.loadWishTree();
+ }
+ }
+ } catch (err) {
+ console.error('许愿失败:', err);
+ wx.showToast({ title: '许愿失败,请重试', icon: 'none' });
+ }
+ },
+
+ // 微信支付
+ async wxPay(orderData) {
+ return new Promise((resolve, reject) => {
+ wx.requestPayment({
+ timeStamp: orderData.timeStamp,
+ nonceStr: orderData.nonceStr,
+ package: orderData.package,
+ signType: orderData.signType,
+ paySign: orderData.paySign,
+ success: () => {
+ wx.showToast({ title: '支付成功', icon: 'success' });
+ this.closeCreateModal();
+ this.loadWishTree();
+ resolve();
+ },
+ fail: (err) => {
+ console.error('支付失败:', err);
+ wx.showToast({ title: '支付失败', icon: 'none' });
+ reject(err);
+ },
+ });
+ });
+ },
+
+ // 查看许愿详情
+ viewWishDetail(e) {
+ const id = e.currentTarget.dataset.id;
+ wx.navigateTo({
+ url: `/pages/wish-detail/wish-detail?id=${id}`,
+ });
+ },
+
+ // 分享
+ onShareAppMessage() {
+ return {
+ title: '快来许愿树许下你的愿望吧!',
+ path: '/pages/wish-tree/wish-tree',
+ };
+ },
+});
diff --git a/mini/pages/wish-tree/wish-tree.json b/mini/pages/wish-tree/wish-tree.json
new file mode 100644
index 0000000..961e417
--- /dev/null
+++ b/mini/pages/wish-tree/wish-tree.json
@@ -0,0 +1,5 @@
+{
+ "navigationBarTitleText": "许愿树",
+ "enablePullDownRefresh": true,
+ "backgroundTextStyle": "dark"
+}
diff --git a/mini/pages/wish-tree/wish-tree.wxml b/mini/pages/wish-tree/wish-tree.wxml
new file mode 100644
index 0000000..1f989c8
--- /dev/null
+++ b/mini/pages/wish-tree/wish-tree.wxml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+ {{item.content}}
+ VIP
+
+
+
+
+
+
+
+
+
+
+
+
+ 🎋
+ 免费许愿:20字以内,可能被覆盖
+
+
+ ✨
+ 付费许愿:100字以内,优先展示,长期保留
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 免费许愿
+ 20字以内
+
+
+ 付费许愿
+ 更多特权
+
+
+
+
+
+
+ {{wishContent.length}}/{{wishType === 'free' ? maxFreeLength : maxPaidLength}}
+
+
+
+
+ 选择许愿商品
+
+
+
+ {{item.name}}
+ 展示{{item.duration}}天
+
+ ¥{{item.price / 100}}
+
+
+
+
+
+
+
+
+
diff --git a/mini/pages/wish-tree/wish-tree.wxss b/mini/pages/wish-tree/wish-tree.wxss
new file mode 100644
index 0000000..1a3e1c7
--- /dev/null
+++ b/mini/pages/wish-tree/wish-tree.wxss
@@ -0,0 +1,307 @@
+.container {
+ min-height: 100vh;
+ background: linear-gradient(180deg, #87CEEB 0%, #E0F6FF 100%);
+ padding-bottom: 120rpx;
+}
+
+/* 画布容器 */
+.canvas-container {
+ position: relative;
+ width: 100%;
+ height: 800rpx;
+ overflow: hidden;
+}
+
+.wish-tree-canvas {
+ width: 100%;
+ height: 100%;
+}
+
+/* 许愿条覆盖层 */
+.wishes-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.wish-tag {
+ position: absolute;
+ padding: 8rpx 16rpx;
+ border-radius: 20rpx;
+ font-size: 20rpx;
+ color: #fff;
+ pointer-events: auto;
+ transform: translate(-50%, -50%);
+ box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.2);
+ max-width: 200rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.wish-tag.free {
+ background: linear-gradient(135deg, #FF6B6B, #FF8E8E);
+}
+
+.wish-tag.paid {
+ background: linear-gradient(135deg, #FFD700, #FFA500);
+}
+
+.wish-tag.robot {
+ border: 2rpx dashed #fff;
+}
+
+.wish-text {
+ font-size: 20rpx;
+}
+
+.wish-badge {
+ position: absolute;
+ top: -8rpx;
+ right: -8rpx;
+ background: #FF4500;
+ color: #fff;
+ font-size: 16rpx;
+ padding: 2rpx 8rpx;
+ border-radius: 10rpx;
+}
+
+/* 操作栏 */
+.action-bar {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 20rpx 30rpx;
+ background: rgba(255,255,255,0.95);
+ box-shadow: 0 -2rpx 20rpx rgba(0,0,0,0.1);
+}
+
+.btn-wish {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10rpx;
+ background: linear-gradient(135deg, #C41E3A, #E74C3C);
+ color: #fff;
+ font-size: 32rpx;
+ padding: 24rpx;
+ border-radius: 50rpx;
+ border: none;
+}
+
+.btn-wish .icon {
+ font-size: 36rpx;
+}
+
+/* 提示区域 */
+.tips-section {
+ padding: 30rpx;
+ margin-top: 20rpx;
+}
+
+.tip-item {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ padding: 20rpx;
+ background: rgba(255,255,255,0.8);
+ border-radius: 16rpx;
+ margin-bottom: 16rpx;
+}
+
+.tip-icon {
+ font-size: 36rpx;
+}
+
+.tip-text {
+ font-size: 26rpx;
+ color: #666;
+}
+
+/* 弹窗 */
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+}
+
+.modal-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.5);
+}
+
+.modal-content {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: #fff;
+ border-radius: 32rpx 32rpx 0 0;
+ max-height: 80vh;
+ overflow: hidden;
+}
+
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 30rpx;
+ border-bottom: 1rpx solid #eee;
+}
+
+.modal-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+}
+
+.modal-close {
+ font-size: 48rpx;
+ color: #999;
+ padding: 10rpx;
+}
+
+.modal-body {
+ padding: 30rpx;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+/* 类型选择 */
+.type-selector {
+ display: flex;
+ gap: 20rpx;
+ margin-bottom: 30rpx;
+}
+
+.type-item {
+ flex: 1;
+ padding: 24rpx;
+ border: 2rpx solid #eee;
+ border-radius: 16rpx;
+ text-align: center;
+}
+
+.type-item.active {
+ border-color: #C41E3A;
+ background: #FFF5F5;
+}
+
+.type-name {
+ display: block;
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 8rpx;
+}
+
+.type-desc {
+ font-size: 22rpx;
+ color: #999;
+}
+
+/* 输入框 */
+.wish-input {
+ width: 100%;
+ height: 200rpx;
+ padding: 20rpx;
+ border: 2rpx solid #eee;
+ border-radius: 16rpx;
+ font-size: 28rpx;
+ box-sizing: border-box;
+}
+
+.input-count {
+ text-align: right;
+ font-size: 22rpx;
+ color: #999;
+ margin-top: 10rpx;
+}
+
+/* 商品选择 */
+.product-section {
+ margin-top: 30rpx;
+}
+
+.section-title {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 20rpx;
+}
+
+.product-list {
+ display: flex;
+ flex-direction: column;
+ gap: 16rpx;
+}
+
+.product-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 24rpx;
+ border: 2rpx solid #eee;
+ border-radius: 16rpx;
+}
+
+.product-item.active {
+ border-color: #C41E3A;
+ background: #FFF5F5;
+}
+
+.product-name {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+}
+
+.product-desc {
+ font-size: 22rpx;
+ color: #999;
+ margin-top: 8rpx;
+}
+
+.product-price {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #C41E3A;
+}
+
+/* 底部按钮 */
+.modal-footer {
+ display: flex;
+ gap: 20rpx;
+ padding: 30rpx;
+ border-top: 1rpx solid #eee;
+}
+
+.modal-footer button {
+ flex: 1;
+ padding: 24rpx;
+ border-radius: 50rpx;
+ font-size: 28rpx;
+}
+
+.btn-outline {
+ background: #fff;
+ border: 2rpx solid #C41E3A;
+ color: #C41E3A;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, #C41E3A, #E74C3C);
+ color: #fff;
+ border: none;
+}
diff --git a/mini/utils/request.js b/mini/utils/request.js
new file mode 100644
index 0000000..c14cdf0
--- /dev/null
+++ b/mini/utils/request.js
@@ -0,0 +1,67 @@
+// 网络请求封装
+const BASE_URL = 'http://localhost:8080'; // 开发环境
+
+// 请求拦截器
+function request(options) {
+ return new Promise((resolve, reject) => {
+ // 获取 token
+ const token = wx.getStorageSync('token');
+
+ wx.request({
+ url: BASE_URL + options.url,
+ method: options.method || 'GET',
+ data: options.data || {},
+ header: {
+ 'Content-Type': 'application/json',
+ 'Authorization': token ? `Bearer ${token}` : '',
+ ...options.header,
+ },
+ success: (res) => {
+ if (res.statusCode === 200) {
+ resolve(res.data);
+ } else if (res.statusCode === 401) {
+ // 未授权,跳转登录
+ wx.navigateTo({ url: '/pages/settings/settings' });
+ reject(new Error('未授权'));
+ } else {
+ reject(new Error(res.data.msg || '请求失败'));
+ }
+ },
+ fail: (err) => {
+ console.error('请求失败:', err);
+ reject(err);
+ },
+ });
+ });
+}
+
+// 上传文件
+function uploadFile(options) {
+ return new Promise((resolve, reject) => {
+ const token = wx.getStorageSync('token');
+
+ wx.uploadFile({
+ url: BASE_URL + options.url,
+ filePath: options.filePath,
+ name: options.name || 'file',
+ header: {
+ 'Authorization': token ? `Bearer ${token}` : '',
+ },
+ formData: options.formData || {},
+ success: (res) => {
+ if (res.statusCode === 200) {
+ resolve(JSON.parse(res.data));
+ } else {
+ reject(new Error('上传失败'));
+ }
+ },
+ fail: reject,
+ });
+ });
+}
+
+module.exports = {
+ request,
+ uploadFile,
+ BASE_URL,
+};
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..6dad13f
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,96 @@
+# 祈福小助手后端服务
+
+基于 Go + Gin + Inertia.js 的后端服务。
+
+## 功能模块
+
+- **用户模块** — 微信登录、资料修改、授权管理
+- **支付模块** — 微信支付集成、订单管理
+- **许愿树** — 许愿发布、付费许愿、机器人自动许愿
+- **管理后台** — 基于 Inertia.js 的前后端分离管理界面
+
+## 技术栈
+
+- **后端**: Go 1.22 + Gin + GORM
+- **前端**: Inertia.js + React/Vue(待选择)
+- **数据库**: MySQL
+- **缓存**: Redis
+
+## 快速开始
+
+### 开发环境
+
+```bash
+# 安装依赖
+go mod download
+
+# 启动开发服务器
+./scripts/dev.sh
+```
+
+### 生产构建
+
+```bash
+# 构建
+./scripts/build.sh
+
+# 运行
+./bin/lunar-server
+```
+
+## 项目结构
+
+```
+server/
+├── cmd/ # 入口文件
+│ └── main.go
+├── internal/ # 内部包
+│ ├── config/ # 配置
+│ ├── handler/ # 处理器
+│ ├── middleware/ # 中间件
+│ ├── model/ # 数据模型
+│ └── service/ # 业务逻辑
+├── web/ # Inertia.js 前端
+│ ├── src/
+│ └── public/
+├── migrations/ # 数据库迁移
+├── scripts/ # 脚本
+└── go.mod
+```
+
+## API 接口
+
+### 用户接口
+
+- `POST /api/user/login` — 用户登录
+- `POST /api/user/logout` — 用户登出
+- `GET /api/user/profile` — 获取用户资料
+- `PUT /api/user/profile` — 更新用户资料
+- `POST /api/user/auth` — 微信授权
+
+### 支付接口
+
+- `POST /api/pay/create` — 创建订单
+- `POST /api/pay/notify` — 支付回调
+- `GET /api/pay/status/:orderId` — 查询支付状态
+
+### 订单接口
+
+- `GET /api/order/list` — 订单列表
+- `GET /api/order/detail/:id` — 订单详情
+- `POST /api/order/cancel/:id` — 取消订单
+
+### 许愿接口
+
+- `GET /api/wish/tree` — 获取许愿树
+- `POST /api/wish/create` — 创建许愿
+- `GET /api/wish/list` — 许愿列表
+- `DELETE /api/wish/:id` — 删除许愿
+
+## 环境变量
+
+参考 `.env.local` 文件配置。
+
+## 数据库
+
+执行 `migrations/001_init.sql` 初始化数据库。
diff --git a/server/cmd/main.go b/server/cmd/main.go
new file mode 100644
index 0000000..017b64d
--- /dev/null
+++ b/server/cmd/main.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+ "log"
+ "os"
+
+ "github.com/gin-gonic/gin"
+ "github.com/joho/godotenv"
+ "github.com/gouki/lunar-server/internal/config"
+ "github.com/gouki/lunar-server/internal/handler"
+ "github.com/gouki/lunar-server/internal/middleware"
+)
+
+func main() {
+ // 加载环境变量
+ if err := godotenv.Load("../.env.local"); err != nil {
+ log.Println("No .env.local file found, using system environment")
+ }
+
+ // 初始化配置
+ cfg := config.Load()
+
+ // 设置 Gin 模式
+ if cfg.Server.Env == "production" {
+ gin.SetMode(gin.ReleaseMode)
+ }
+
+ // 创建路由
+ r := gin.Default()
+
+ // 中间件
+ r.Use(middleware.CORS())
+ r.Use(middleware.Logger())
+
+ // 静态文件(Inertia.js 前端构建产物)
+ r.Static("/build", "./web/public/build")
+ r.LoadHTMLGlob("web/*.html")
+
+ // API 路由
+ api := r.Group("/api")
+ {
+ // 用户相关
+ user := api.Group("/user")
+ {
+ user.POST("/login", handler.UserLogin)
+ user.POST("/logout", handler.UserLogout)
+ user.GET("/profile", middleware.Auth(), handler.GetUserProfile)
+ user.PUT("/profile", middleware.Auth(), handler.UpdateUserProfile)
+ user.POST("/auth", handler.WechatAuth)
+ }
+
+ // 支付相关
+ pay := api.Group("/pay")
+ {
+ pay.POST("/create", middleware.Auth(), handler.CreateOrder)
+ pay.POST("/notify", handler.PayNotify)
+ pay.GET("/status/:orderId", middleware.Auth(), handler.GetPayStatus)
+ }
+
+ // 订单管理
+ order := api.Group("/order")
+ {
+ order.GET("/list", middleware.Auth(), handler.GetOrderList)
+ order.GET("/detail/:id", middleware.Auth(), handler.GetOrderDetail)
+ order.POST("/cancel/:id", middleware.Auth(), handler.CancelOrder)
+ }
+
+ // 许愿树
+ wish := api.Group("/wish")
+ {
+ wish.GET("/tree", handler.GetWishTree)
+ wish.POST("/create", middleware.Auth(), handler.CreateWish)
+ wish.GET("/list", handler.GetWishList)
+ wish.DELETE("/:id", middleware.Auth(), handler.DeleteWish)
+ }
+ }
+
+ // 管理后台(Inertia.js)
+ admin := r.Group("/admin")
+ {
+ admin.GET("/", handler.AdminIndex)
+ admin.GET("/users", handler.AdminUsers)
+ admin.GET("/orders", handler.AdminOrders)
+ admin.GET("/wishes", handler.AdminWishes)
+ admin.GET("/settings", handler.AdminSettings)
+ }
+
+ // 启动服务器
+ addr := ":" + cfg.Server.Port
+ log.Printf("Server starting on %s", addr)
+ if err := r.Run(addr); err != nil {
+ log.Fatal("Failed to start server:", err)
+ }
+}
diff --git a/server/go.mod b/server/go.mod
new file mode 100644
index 0000000..61fe28a
--- /dev/null
+++ b/server/go.mod
@@ -0,0 +1,41 @@
+module github.com/gouki/lunar-server
+
+go 1.22
+
+require (
+ github.com/gin-gonic/gin v1.9.1
+ github.com/go-inertia/inertia-go v1.0.0
+ github.com/golang-jwt/jwt/v5 v5.2.0
+ github.com/go-sql-driver/mysql v1.7.1
+ github.com/joho/godotenv v1.5.1
+ github.com/redis/go-redis/v9 v9.4.0
+ golang.org/x/crypto v0.17.0
+ gorm.io/gorm v1.25.5
+ gorm.io/driver/mysql v1.5.2
+)
+
+require (
+ github.com/bytedance/sonic v1.9.1 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/leodido/go-urn v1.2.4 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
+ golang.org/x/arch v0.3.0 // indirect
+ golang.org/x/net v0.19.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/server/internal/config/config.go b/server/internal/config/config.go
new file mode 100644
index 0000000..02dfd70
--- /dev/null
+++ b/server/internal/config/config.go
@@ -0,0 +1,82 @@
+package config
+
+import (
+ "os"
+)
+
+type Config struct {
+ Server ServerConfig
+ Database DatabaseConfig
+ Redis RedisConfig
+ JWT JWTConfig
+ Wechat WechatConfig
+}
+
+type ServerConfig struct {
+ Port string
+ Env string
+}
+
+type DatabaseConfig struct {
+ Host string
+ Port string
+ User string
+ Password string
+ Name string
+}
+
+type RedisConfig struct {
+ Host string
+ Port string
+ Password string
+ DB int
+}
+
+type JWTConfig struct {
+ Secret string
+}
+
+type WechatConfig struct {
+ AppID string
+ AppSecret string
+ PayKey string
+ MchID string
+}
+
+func Load() *Config {
+ return &Config{
+ Server: ServerConfig{
+ Port: getEnv("SERVER_PORT", "8080"),
+ Env: getEnv("SERVER_ENV", "development"),
+ },
+ Database: DatabaseConfig{
+ Host: getEnv("DB_HOST", "localhost"),
+ Port: getEnv("DB_PORT", "3306"),
+ User: getEnv("DB_USER", "root"),
+ Password: getEnv("DB_PASSWORD", ""),
+ Name: getEnv("DB_NAME", "lunar"),
+ },
+ Redis: RedisConfig{
+ Host: getEnv("REDIS_HOST", "localhost"),
+ Port: getEnv("REDIS_PORT", "6379"),
+ Password: getEnv("REDIS_PASSWORD", ""),
+ DB: 0,
+ },
+ JWT: JWTConfig{
+ Secret: getEnv("JWT_SECRET", "your-secret-key"),
+ },
+ Wechat: WechatConfig{
+ AppID: getEnv("MINI_APP_ID", ""),
+ AppSecret: getEnv("MINI_APP_SECRET", ""),
+ PayKey: getEnv("WECHAT_PAY_APIKEY", ""),
+ MchID: getEnv("WECHAT_PAY_MCHID", ""),
+ },
+ }
+}
+
+func getEnv(key, defaultValue string) string {
+ if value := os.Getenv(key); value != "" {
+ return value
+ }
+ return defaultValue
+}
diff --git a/server/internal/handler/admin.go b/server/internal/handler/admin.go
new file mode 100644
index 0000000..19c4c0c
--- /dev/null
+++ b/server/internal/handler/admin.go
@@ -0,0 +1,42 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// AdminIndex 管理后台首页
+func AdminIndex(c *gin.Context) {
+ c.HTML(http.StatusOK, "index.html", gin.H{
+ "title": "管理后台",
+ })
+}
+
+// AdminUsers 用户管理
+func AdminUsers(c *gin.Context) {
+ c.HTML(http.StatusOK, "index.html", gin.H{
+ "title": "用户管理",
+ })
+}
+
+// AdminOrders 订单管理
+func AdminOrders(c *gin.Context) {
+ c.HTML(http.StatusOK, "index.html", gin.H{
+ "title": "订单管理",
+ })
+}
+
+// AdminWishes 许愿管理
+func AdminWishes(c *gin.Context) {
+ c.HTML(http.StatusOK, "index.html", gin.H{
+ "title": "许愿管理",
+ })
+}
+
+// AdminSettings 系统设置
+func AdminSettings(c *gin.Context) {
+ c.HTML(http.StatusOK, "index.html", gin.H{
+ "title": "系统设置",
+ })
+}
diff --git a/server/internal/handler/order.go b/server/internal/handler/order.go
new file mode 100644
index 0000000..9c336dc
--- /dev/null
+++ b/server/internal/handler/order.go
@@ -0,0 +1,43 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// GetOrderList 获取订单列表
+func GetOrderList(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "list": []interface{}{},
+ "total": 0,
+ },
+ })
+}
+
+// GetOrderDetail 获取订单详情
+func GetOrderDetail(c *gin.Context) {
+ id := c.Param("id")
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "id": id,
+ },
+ })
+}
+
+// CancelOrder 取消订单
+func CancelOrder(c *gin.Context) {
+ id := c.Param("id")
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "id": id,
+ },
+ })
+}
diff --git a/server/internal/handler/pay.go b/server/internal/handler/pay.go
new file mode 100644
index 0000000..c54d66b
--- /dev/null
+++ b/server/internal/handler/pay.go
@@ -0,0 +1,39 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// CreateOrder 创建订单
+func CreateOrder(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "orderId": "example-order-id",
+ },
+ })
+}
+
+// PayNotify 支付回调
+func PayNotify(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": "SUCCESS",
+ "msg": "OK",
+ })
+}
+
+// GetPayStatus 获取支付状态
+func GetPayStatus(c *gin.Context) {
+ orderId := c.Param("orderId")
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "orderId": orderId,
+ "status": "pending",
+ },
+ })
+}
diff --git a/server/internal/handler/user.go b/server/internal/handler/user.go
new file mode 100644
index 0000000..6f5f994
--- /dev/null
+++ b/server/internal/handler/user.go
@@ -0,0 +1,58 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// UserLogin 用户登录
+func UserLogin(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "token": "example-token",
+ },
+ })
+}
+
+// UserLogout 用户登出
+func UserLogout(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ })
+}
+
+// GetUserProfile 获取用户资料
+func GetUserProfile(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "id": 1,
+ "nickname": "用户昵称",
+ "avatar": "",
+ },
+ })
+}
+
+// UpdateUserProfile 更新用户资料
+func UpdateUserProfile(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ })
+}
+
+// WechatAuth 微信授权
+func WechatAuth(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "openid": "example-openid",
+ },
+ })
+}
diff --git a/server/internal/handler/wish.go b/server/internal/handler/wish.go
new file mode 100644
index 0000000..21c1810
--- /dev/null
+++ b/server/internal/handler/wish.go
@@ -0,0 +1,57 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// GetWishTree 获取许愿树
+func GetWishTree(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "tree": gin.H{
+ "id": 1,
+ "name": "许愿树",
+ "wishes": []interface{}{},
+ },
+ },
+ })
+}
+
+// CreateWish 创建许愿
+func CreateWish(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "id": 1,
+ },
+ })
+}
+
+// GetWishList 获取许愿列表
+func GetWishList(c *gin.Context) {
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "list": []interface{}{},
+ "total": 0,
+ },
+ })
+}
+
+// DeleteWish 删除许愿
+func DeleteWish(c *gin.Context) {
+ id := c.Param("id")
+ c.JSON(http.StatusOK, gin.H{
+ "code": 0,
+ "msg": "success",
+ "data": gin.H{
+ "id": id,
+ },
+ })
+}
diff --git a/server/internal/middleware/middleware.go b/server/internal/middleware/middleware.go
new file mode 100644
index 0000000..626809a
--- /dev/null
+++ b/server/internal/middleware/middleware.go
@@ -0,0 +1,46 @@
+package middleware
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// CORS 跨域中间件
+func CORS() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+ c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+ c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
+
+ if c.Request.Method == "OPTIONS" {
+ c.AbortWithStatus(http.StatusNoContent)
+ return
+ }
+
+ c.Next()
+ }
+}
+
+// Logger 日志中间件
+func Logger() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ c.Next()
+ }
+}
+
+// Auth 认证中间件
+func Auth() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ token := c.GetHeader("Authorization")
+ if token == "" {
+ c.JSON(http.StatusUnauthorized, gin.H{
+ "code": 401,
+ "msg": "unauthorized",
+ })
+ c.Abort()
+ return
+ }
+ c.Next()
+ }
+}
diff --git a/server/internal/model/order.go b/server/internal/model/order.go
new file mode 100644
index 0000000..54fec37
--- /dev/null
+++ b/server/internal/model/order.go
@@ -0,0 +1,32 @@
+package model
+
+import (
+ "time"
+)
+
+// Order 订单模型
+type Order struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OrderNo string `gorm:"uniqueIndex;size:32" json:"orderNo"` // 订单号
+ UserID uint `gorm:"index" json:"userId"`
+ Type string `gorm:"size:20" json:"type"` // wish:许愿 vip:会员
+ ProductID uint `json:"productId"`
+ ProductName string `gorm:"size:100" json:"productName"`
+ Amount int `json:"amount"` // 金额(分)
+ Status string `gorm:"size:20;default:'pending'" json:"status"` // pending:待支付 paid:已支付 cancelled:已取消 refunded:已退款
+ PayTime *time.Time `json:"payTime"`
+ ExpireTime *time.Time `json:"expireTime"`
+ Remark string `gorm:"size:255" json:"remark"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+// OrderItem 订单项
+type OrderItem struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OrderID uint `gorm:"index" json:"orderId"`
+ ProductID uint `json:"productId"`
+ Name string `gorm:"size:100" json:"name"`
+ Price int `json:"price"` // 单价(分)
+ Quantity int `json:"quantity"` // 数量
+}
diff --git a/server/internal/model/user.go b/server/internal/model/user.go
new file mode 100644
index 0000000..ed580ab
--- /dev/null
+++ b/server/internal/model/user.go
@@ -0,0 +1,34 @@
+package model
+
+import (
+ "time"
+)
+
+// User 用户模型
+type User struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OpenID string `gorm:"uniqueIndex;size:64" json:"openId"`
+ UnionID string `gorm:"size:64" json:"unionId"`
+ Nickname string `gorm:"size:64" json:"nickname"`
+ Avatar string `gorm:"size:255" json:"avatar"`
+ Gender int `gorm:"default:0" json:"gender"` // 0:未知 1:男 2:女
+ Phone string `gorm:"size:20" json:"phone"`
+ Email string `gorm:"size:100" json:"email"`
+ Status int `gorm:"default:1" json:"status"` // 1:正常 0:禁用
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+// UserProfile 用户资料扩展
+type UserProfile struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UserID uint `gorm:"index" json:"userId"`
+ RealName string `gorm:"size:32" json:"realName"`
+ Birthday string `gorm:"size:10" json:"birthday"` // YYYY-MM-DD
+ BirthTime string `gorm:"size:8" json:"birthTime"` // HH:mm:ss
+ Gender int `gorm:"default:0" json:"gender"`
+ Zodiac string `gorm:"size:10" json:"zodiac"` // 生肖
+ Constellation string `gorm:"size:20" json:"constellation"` // 星座
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
diff --git a/server/internal/model/wish.go b/server/internal/model/wish.go
new file mode 100644
index 0000000..ebf31e7
--- /dev/null
+++ b/server/internal/model/wish.go
@@ -0,0 +1,41 @@
+package model
+
+import (
+ "time"
+)
+
+// Wish 许愿模型
+type Wish struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UserID uint `gorm:"index" json:"userId"`
+ TreeID uint `gorm:"index" json:"treeId"` // 许愿树ID
+ Content string `gorm:"size:500" json:"content"` // 许愿内容
+ Type string `gorm:"size:20;default:'free'" json:"type"` // free:免费 paid:付费
+ Position int `gorm:"default:0" json:"position"` // 位置(用于排序/覆盖)
+ Status int `gorm:"default:1" json:"status"` // 1:正常 0:隐藏/删除
+ IsRobot bool `gorm:"default:false" json:"isRobot"` // 是否机器人发布
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+// WishTree 许愿树模型
+type WishTree struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Name string `gorm:"size:50" json:"name"`
+ Description string `gorm:"size:255" json:"description"`
+ MaxWishes int `gorm:"default:100" json:"maxWishes"` // 最大许愿条数
+ Status int `gorm:"default:1" json:"status"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+// WishProduct 许愿商品(付费许愿)
+type WishProduct struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Name string `gorm:"size:50" json:"name"` // 商品名称
+ Description string `gorm:"size:255" json:"description"`
+ Price int `json:"price"` // 价格(分)
+ Duration int `json:"duration"` // 展示时长(天)
+ Position int `json:"position"` // 优先位置
+ Status int `gorm:"default:1" json:"status"`
+}
diff --git a/server/internal/service/user.go b/server/internal/service/user.go
new file mode 100644
index 0000000..a8c8541
--- /dev/null
+++ b/server/internal/service/user.go
@@ -0,0 +1,26 @@
+package service
+
+import (
+ "github.com/gouki/lunar-server/internal/model"
+)
+
+// UserService 用户服务
+type UserService struct{}
+
+// GetUserByOpenID 根据OpenID获取用户
+func (s *UserService) GetUserByOpenID(openID string) (*model.User, error) {
+ // TODO: 实现数据库查询
+ return &model.User{}, nil
+}
+
+// CreateUser 创建用户
+func (s *UserService) CreateUser(user *model.User) error {
+ // TODO: 实现数据库创建
+ return nil
+}
+
+// UpdateUser 更新用户
+func (s *UserService) UpdateUser(user *model.User) error {
+ // TODO: 实现数据库更新
+ return nil
+}
diff --git a/server/internal/service/wish.go b/server/internal/service/wish.go
new file mode 100644
index 0000000..11cc337
--- /dev/null
+++ b/server/internal/service/wish.go
@@ -0,0 +1,38 @@
+package service
+
+import (
+ "github.com/gouki/lunar-server/internal/model"
+)
+
+// WishService 许愿服务
+type WishService struct{}
+
+// GetWishTree 获取许愿树
+func (s *WishService) GetWishTree(treeID uint) (*model.WishTree, error) {
+ // TODO: 实现数据库查询
+ return &model.WishTree{}, nil
+}
+
+// CreateWish 创建许愿
+func (s *WishService) CreateWish(wish *model.Wish) error {
+ // TODO: 实现数据库创建
+ return nil
+}
+
+// GetWishList 获取许愿列表
+func (s *WishService) GetWishList(treeID uint, page, pageSize int) ([]*model.Wish, int64, error) {
+ // TODO: 实现数据库查询
+ return []*model.Wish{}, 0, nil
+}
+
+// DeleteWish 删除许愿
+func (s *WishService) DeleteWish(id uint) error {
+ // TODO: 实现数据库删除
+ return nil
+}
+
+// CreateRobotWish 创建机器人许愿
+func (s *WishService) CreateRobotWish(treeID uint) error {
+ // TODO: 实现机器人自动许愿
+ return nil
+}
diff --git a/server/migrations/001_init.sql b/server/migrations/001_init.sql
new file mode 100644
index 0000000..b8089a7
--- /dev/null
+++ b/server/migrations/001_init.sql
@@ -0,0 +1,104 @@
+-- 用户表
+CREATE TABLE IF NOT EXISTS users (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ open_id VARCHAR(64) NOT NULL UNIQUE COMMENT '微信OpenID',
+ union_id VARCHAR(64) DEFAULT '' COMMENT '微信UnionID',
+ nickname VARCHAR(64) DEFAULT '' COMMENT '昵称',
+ avatar VARCHAR(255) DEFAULT '' COMMENT '头像',
+ gender TINYINT DEFAULT 0 COMMENT '性别 0:未知 1:男 2:女',
+ phone VARCHAR(20) DEFAULT '' COMMENT '手机号',
+ email VARCHAR(100) DEFAULT '' COMMENT '邮箱',
+ status TINYINT DEFAULT 1 COMMENT '状态 1:正常 0:禁用',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ INDEX idx_open_id (open_id),
+ INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
+
+-- 用户资料表
+CREATE TABLE IF NOT EXISTS user_profiles (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
+ real_name VARCHAR(32) DEFAULT '' COMMENT '真实姓名',
+ birthday VARCHAR(10) DEFAULT '' COMMENT '生日 YYYY-MM-DD',
+ birth_time VARCHAR(8) DEFAULT '' COMMENT '出生时间 HH:mm:ss',
+ gender TINYINT DEFAULT 0 COMMENT '性别',
+ zodiac VARCHAR(10) DEFAULT '' COMMENT '生肖',
+ constellation VARCHAR(20) DEFAULT '' COMMENT '星座',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户资料表';
+
+-- 订单表
+CREATE TABLE IF NOT EXISTS orders (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号',
+ user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
+ type VARCHAR(20) NOT NULL COMMENT '订单类型 wish:许愿 vip:会员',
+ product_id BIGINT UNSIGNED DEFAULT 0 COMMENT '商品ID',
+ product_name VARCHAR(100) DEFAULT '' COMMENT '商品名称',
+ amount INT NOT NULL DEFAULT 0 COMMENT '金额(分)',
+ status VARCHAR(20) DEFAULT 'pending' COMMENT '状态 pending:待支付 paid:已支付 cancelled:已取消 refunded:已退款',
+ pay_time TIMESTAMP NULL COMMENT '支付时间',
+ expire_time TIMESTAMP NULL COMMENT '过期时间',
+ remark VARCHAR(255) DEFAULT '' COMMENT '备注',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ INDEX idx_order_no (order_no),
+ INDEX idx_user_id (user_id),
+ INDEX idx_status (status),
+ INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
+
+-- 许愿树表
+CREATE TABLE IF NOT EXISTS wish_trees (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL COMMENT '名称',
+ description VARCHAR(255) DEFAULT '' COMMENT '描述',
+ max_wishes INT DEFAULT 100 COMMENT '最大许愿条数',
+ status TINYINT DEFAULT 1 COMMENT '状态',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='许愿树表';
+
+-- 许愿表
+CREATE TABLE IF NOT EXISTS wishes (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
+ tree_id BIGINT UNSIGNED NOT NULL COMMENT '许愿树ID',
+ content VARCHAR(500) NOT NULL COMMENT '许愿内容',
+ type VARCHAR(20) DEFAULT 'free' COMMENT '类型 free:免费 paid:付费',
+ position INT DEFAULT 0 COMMENT '位置',
+ status TINYINT DEFAULT 1 COMMENT '状态 1:正常 0:隐藏/删除',
+ is_robot TINYINT DEFAULT 0 COMMENT '是否机器人发布',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ INDEX idx_user_id (user_id),
+ INDEX idx_tree_id (tree_id),
+ INDEX idx_status (status),
+ INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='许愿表';
+
+-- 许愿商品表
+CREATE TABLE IF NOT EXISTS wish_products (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL COMMENT '商品名称',
+ description VARCHAR(255) DEFAULT '' COMMENT '描述',
+ price INT NOT NULL DEFAULT 0 COMMENT '价格(分)',
+ duration INT DEFAULT 7 COMMENT '展示时长(天)',
+ position INT DEFAULT 0 COMMENT '优先位置',
+ status TINYINT DEFAULT 1 COMMENT '状态',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='许愿商品表';
+
+-- 插入默认许愿树
+INSERT IGNORE INTO wish_trees (id, name, description, max_wishes) VALUES
+(1, '祈福许愿树', '许下美好愿望,祈福平安顺遂', 100);
+
+-- 插入默认许愿商品
+INSERT IGNORE INTO wish_products (id, name, description, price, duration, position) VALUES
+(1, '普通许愿条', '基础许愿条,展示7天', 100, 7, 0),
+(2, '精品许愿条', '精品许愿条,展示30天,优先位置', 500, 30, 10),
+(3, '至尊许愿条', '至尊许愿条,展示90天,置顶显示', 2000, 90, 100);
diff --git a/server/scripts/build.sh b/server/scripts/build.sh
new file mode 100644
index 0000000..5c5907d
--- /dev/null
+++ b/server/scripts/build.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# 生产环境构建脚本
+
+echo "Building Lunar Server..."
+
+# 创建输出目录
+mkdir -p bin
+
+# 编译 Go 后端
+echo "Building Go backend..."
+go build -o bin/lunar-server cmd/main.go
+
+# 编译前端(如果存在)
+if [ -d "web" ]; then
+ echo "Building frontend..."
+ cd web
+ npm install
+ npm run build
+ cd ..
+fi
+
+echo "Build complete!"
+echo "Output: bin/lunar-server"
diff --git a/server/scripts/dev.sh b/server/scripts/dev.sh
new file mode 100644
index 0000000..47fad8d
--- /dev/null
+++ b/server/scripts/dev.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# 开发环境启动脚本
+
+echo "Starting Lunar Server..."
+
+# 检查 .env.local 文件
+if [ ! -f "../.env.local" ]; then
+ echo "Warning: .env.local not found, using default configuration"
+fi
+
+# 启动服务器
+go run cmd/main.go