加油
努力

如何让微信小程序与Web应用共享同一套API接口?

让微信小程序与Web应用共享同一套API接口是完全可行的,关键在于设计一套通用、安全、跨平台兼容的后端API。以下是详细的实现方案和最佳实践:

1. 统一API设计原则

RESTful API设计

// 示例:用户相关API
GET    /api/users/:id          // 获取用户信息
POST   /api/users/login        // 用户登录
PUT    /api/users/:id          // 更新用户信息
DELETE /api/users/:id          // 删除用户

统一响应格式

{
  "code": 200,
  "message": "success",
  "data": {
    "userInfo": {
      "id": 1,
      "name": "张三"
    }
  },
  "timestamp": 1640995200000
}

2. 认证机制统一

JWT认证方案

// 后端统一处理
app.use(async (ctx, next) => {
  const token = ctx.get('Authorization') || 
                ctx.query.token || 
                ctx.request.body.token;

  if (token) {
    try {
      const decoded = jwt.verify(token, SECRET_KEY);
      ctx.state.user = decoded;
    } catch (error) {
      ctx.status = 401;
      ctx.body = { code: 401, message: 'Token无效' };
      return;
    }
  }
  await next();
});

微信小程序登录流程

// 小程序端
async function login() {
  const res = await wx.login();
  const response = await request('/api/auth/wx-login', {
    method: 'POST',
    data: { code: res.code }
  });

  // 存储token
  wx.setStorageSync('token', response.data.token);
  setToken(response.data.token);
}

// Web端
async function webLogin(credentials) {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(credentials)
  });

  const data = await response.json();
  localStorage.setItem('token', data.token);
}

3. 跨域配置

后端CORS配置

// Node.js + Koa示例
app.use(cors({
  origin: function (ctx) {
    // 允许小程序和Web域名
    const origins = [
      'https://your-web-domain.com',
      'https://servicewechat.com'
    ];

    if (origins.includes(ctx.header.origin)) {
      return ctx.header.origin;
    }

    return 'https://your-web-domain.com'; // 默认
  },
  credentials: true,
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
}));

4. 请求封装统一

统一请求工具类

class ApiService {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.platform = this.detectPlatform();
  }

  detectPlatform() {
    if (typeof wx !== 'undefined') return 'miniapp';
    if (typeof window !== 'undefined') return 'web';
    return 'unknown';
  }

  async request(options) {
    const config = {
      url: this.baseURL + options.url,
      method: options.method || 'GET',
      header: {
        'Content-Type': 'application/json',
        'Authorization': this.getToken(),
        'X-Platform': this.platform
      },
      ...options
    };

    try {
      let response;

      if (this.platform === 'miniapp') {
        response = await this.miniappRequest(config);
      } else {
        response = await this.webRequest(config);
      }

      return this.handleResponse(response);

    } catch (error) {
      return this.handleError(error);
    }
  }

  async miniappRequest(config) {
    return new Promise((resolve, reject) => {
      wx.request({
        ...config,
        success: resolve,
        fail: reject
      });
    });
  }

  async webRequest(config) {
    const response = await fetch(config.url, {
      method: config.method,
      headers: config.header,
      body: config.data ? JSON.stringify(config.data) : undefined
    });

    return {
      statusCode: response.status,
      data: await response.json()
    };
  }

  getToken() {
    if (this.platform === 'miniapp') {
      return wx.getStorageSync('token');
    } else {
      return localStorage.getItem('token');
    }
  }

  handleResponse(response) {
    if (response.statusCode === 200) {
      if (response.data.code === 200) {
        return response.data;
      } else {
        throw new Error(response.data.message);
      }
    } else {
      throw new Error(`HTTP ${response.statusCode}`);
    }
  }
}

5. 环境配置管理

配置文件

// config.js
const ENV_CONFIG = {
  development: {
    apiBaseURL: 'http://localhost:3000',
    debug: true
  },
  production: {
    apiBaseURL: 'https://api.yourdomain.com',
    debug: false
  }
};

export default ENV_CONFIG[process.env.NODE_ENV || 'development'];

小程序配置

// project.config.json
{
  "setting": {
    "urlCheck": false
  },
  "env": {
    "production": {
      "apiBaseURL": "https://api.yourdomain.com"
    },
    "development": {
      "apiBaseURL": "http://localhost:3000"
    }
  }
}

6. 安全性考虑

接口安全措施

// 频率限制
app.use(rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100 // 限制每个IP 100次请求
}));

// 参数验证
app.post('/api/users/login', validate({
  body: {
    username: Joi.string().min(3).max(30).required(),
    password: Joi.string().min(6).required()
  }
}), loginHandler);

// 敏感操作日志
app.use(async (ctx, next) => {
  const startTime = Date.now();
  await next();

  console.log({
    timestamp: new Date().toISOString(),
    ip: ctx.ip,
    method: ctx.method,
    url: ctx.url,
    userAgent: ctx.get('User-Agent'),
    duration: Date.now() - startTime,
    status: ctx.status
  });
});

7. 实际使用示例

获取用户信息

// 服务层
const api = new ApiService('/api');

// 小程序页面
Page({
  async onLoad() {
    try {
      const result = await api.request({
        url: '/users/profile',
        method: 'GET'
      });

      this.setData({ userInfo: result.data.userInfo });
    } catch (error) {
      wx.showToast({ title: error.message, icon: 'none' });
    }
  }
});

// Web组件
function UserProfile() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    api.request({ url: '/users/profile' })
      .then(result => setUser(result.data.userInfo))
      .catch(error => console.error(error));
  }, []);

  return <div>{user?.name}</div>;
}

8. 最佳实践总结

  1. 统一API规范:保持RESTful风格,统一错误码
  2. 灵活认证:支持多种登录方式(微信、手机号、邮箱等)
  3. 跨域处理:正确配置CORS,区分环境
  4. 错误处理:统一错误处理机制,提供友好提示
  5. 性能优化:合理使用缓存,减少重复请求
  6. 监控日志:记录关键操作,便于问题排查

通过以上方案,可以实现微信小程序和Web应用无缝共享同一套API接口,提高开发效率和系统维护性。

云服务器