feat:add domain config
This commit is contained in:
		@@ -3,6 +3,7 @@ import path from 'path';
 | 
			
		||||
export const getContentType = (filePath: string) => {
 | 
			
		||||
  const extname = path.extname(filePath);
 | 
			
		||||
  const contentType = {
 | 
			
		||||
    '.txt': 'text/plain',
 | 
			
		||||
    '.html': 'text/html',
 | 
			
		||||
    '.js': 'text/javascript',
 | 
			
		||||
    '.css': 'text/css',
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import { pipeline } from 'stream';
 | 
			
		||||
import { promisify } from 'util';
 | 
			
		||||
const pipelineAsync = promisify(pipeline);
 | 
			
		||||
 | 
			
		||||
const { resources } = useConfig<{ resources: string }>();
 | 
			
		||||
const { resources, api } = useConfig<{ resources: string; api: { host: string; testHost: string } }>();
 | 
			
		||||
const fileStore = useFileStore('upload');
 | 
			
		||||
 | 
			
		||||
const demoData = {
 | 
			
		||||
@@ -36,30 +36,6 @@ const demoData = {
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
const demoData2 = {
 | 
			
		||||
  user: 'root',
 | 
			
		||||
  key: 'codeflow',
 | 
			
		||||
  appType: 'web-single', //
 | 
			
		||||
  version: '0.0.1',
 | 
			
		||||
  domain: null,
 | 
			
		||||
  type: 'oss', // 是否使用oss
 | 
			
		||||
  data: {
 | 
			
		||||
    files: [
 | 
			
		||||
      {
 | 
			
		||||
        name: 'index.html',
 | 
			
		||||
        path: 'root/codeflow/0.0.1/index.html',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'assets/index-14y4J8dP.js',
 | 
			
		||||
        path: 'root/codeflow/0.0.1/assets/index-14y4J8dP.js',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'assets/index-C-libw4a.css',
 | 
			
		||||
        path: 'root/codeflow/0.0.1/assets/index-C-libw4a.css',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
type UserAppOptions = {
 | 
			
		||||
  user: string;
 | 
			
		||||
  app: string;
 | 
			
		||||
@@ -94,16 +70,89 @@ export class UserApp {
 | 
			
		||||
    const value = await redis.hget(key, appFileUrl);
 | 
			
		||||
    return value;
 | 
			
		||||
  }
 | 
			
		||||
  static async getDomainApp(domain: string) {
 | 
			
		||||
    const key = 'domain:' + domain;
 | 
			
		||||
    const value = await redis.get(key);
 | 
			
		||||
    if (value) {
 | 
			
		||||
      const [_user, _app] = value.split(':');
 | 
			
		||||
      return {
 | 
			
		||||
        user: _user,
 | 
			
		||||
        app: _app,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取域名对应的用户和应用
 | 
			
		||||
    const isDev = process.env.NODE_ENV === 'development';
 | 
			
		||||
    const fetchTestUrl = 'http://' + api.testHost + '/api/router';
 | 
			
		||||
    const fetchUrl = 'http://' + api.host + '/api/router';
 | 
			
		||||
    const fetchRes = await fetch(isDev ? fetchTestUrl : fetchUrl, {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      headers: {
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
      },
 | 
			
		||||
      body: JSON.stringify({
 | 
			
		||||
        path: 'app',
 | 
			
		||||
        key: 'getDomainApp',
 | 
			
		||||
        data: {
 | 
			
		||||
          domain,
 | 
			
		||||
        },
 | 
			
		||||
      }),
 | 
			
		||||
    }).then((res) => res.json());
 | 
			
		||||
    if (fetchRes?.code !== 200) {
 | 
			
		||||
      console.log('fetchRes is error', fetchRes);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    const fetchData = fetchRes.data;
 | 
			
		||||
    const data = {
 | 
			
		||||
      user: fetchData.user,
 | 
			
		||||
      app: fetchData.key,
 | 
			
		||||
    };
 | 
			
		||||
    redis.set(key, data.user + ':' + data.app, 'EX', 60 * 60 * 24 * 7); // 24小时
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
  async setCacheData() {
 | 
			
		||||
    const app = this.app;
 | 
			
		||||
    const user = this.user;
 | 
			
		||||
    const key = 'user:app:' + app + ':' + user;
 | 
			
		||||
    // 如果demoData 不存在则返回
 | 
			
		||||
    if (!demoData2) {
 | 
			
		||||
    const isDev = process.env.NODE_ENV === 'development';
 | 
			
		||||
    const fetchTestUrl = 'http://' + api.testHost + '/api/router';
 | 
			
		||||
    const fetchUrl = 'http://' + api.host + '/api/router';
 | 
			
		||||
    const fetchRes = await fetch(isDev ? fetchTestUrl : fetchUrl, {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      headers: {
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
      },
 | 
			
		||||
      body: JSON.stringify({
 | 
			
		||||
        path: 'app',
 | 
			
		||||
        key: 'getApp',
 | 
			
		||||
        data: {
 | 
			
		||||
          user,
 | 
			
		||||
          key: app,
 | 
			
		||||
        },
 | 
			
		||||
      }),
 | 
			
		||||
    }).then((res) => res.json());
 | 
			
		||||
    if (fetchRes?.code !== 200) {
 | 
			
		||||
      console.log('fetchRes is error', fetchRes);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    const value = await downloadUserAppFiles(user, app, demoData2);
 | 
			
		||||
    const valueIndexHtml = value.data.files.find((file) => file.name === 'index.html');
 | 
			
		||||
    const fetchData = fetchRes.data;
 | 
			
		||||
    if (!fetchData.type) {
 | 
			
		||||
      // console.error('fetchData type is error', fetchData);
 | 
			
		||||
      // return false;
 | 
			
		||||
      fetchData.type = 'oss';
 | 
			
		||||
    }
 | 
			
		||||
    const value = await downloadUserAppFiles(user, app, fetchData);
 | 
			
		||||
    if (value.data.files.length === 0) {
 | 
			
		||||
      console.error('root files length is zero', user, app);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    let valueIndexHtml = value.data.files.find((file) => file.name === 'index.html');
 | 
			
		||||
    if (!valueIndexHtml) {
 | 
			
		||||
      valueIndexHtml = value.data.files.find((file) => file.name === 'index.js');
 | 
			
		||||
      if (!valueIndexHtml) {
 | 
			
		||||
        valueIndexHtml = value.data.files[0];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    await redis.set(key, JSON.stringify(value));
 | 
			
		||||
    await redis.set('user:app:exist:' + app + ':' + user, valueIndexHtml.path, 'EX', 60 * 60 * 24 * 7); // 24小时
 | 
			
		||||
    const files = value.data.files;
 | 
			
		||||
@@ -140,9 +189,6 @@ export class UserApp {
 | 
			
		||||
    // 删除所有文件
 | 
			
		||||
    deleteUserAppFiles(user, app);
 | 
			
		||||
  }
 | 
			
		||||
  async getData() {
 | 
			
		||||
    return demoData;
 | 
			
		||||
  }
 | 
			
		||||
  async close() {
 | 
			
		||||
    // 关闭连接
 | 
			
		||||
    await redis.quit();
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,12 @@ import fs from 'fs';
 | 
			
		||||
import { useConfig } from '@abearxiong/use-config';
 | 
			
		||||
import { redis } from './redis/redis.ts';
 | 
			
		||||
import { getContentType } from './get-content-type.ts';
 | 
			
		||||
const { api, domain } = useConfig<{
 | 
			
		||||
const { api, domain, allowedOrigins } = useConfig<{
 | 
			
		||||
  api: {
 | 
			
		||||
    host: string;
 | 
			
		||||
  };
 | 
			
		||||
  domain: string;
 | 
			
		||||
  allowedOrigins: string[];
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const fileStore = useFileStore('upload');
 | 
			
		||||
@@ -19,6 +20,18 @@ console.log('filePath', fileStore);
 | 
			
		||||
const noProxyUrl = ['/', '/favicon.ico'];
 | 
			
		||||
export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
 | 
			
		||||
  const dns = getDNS(req);
 | 
			
		||||
  // 配置可以跨域
 | 
			
		||||
  // 配置可以访问的域名 localhost, xiongxiao.me
 | 
			
		||||
  const _orings = allowedOrigins || [];
 | 
			
		||||
  const host = dns.hostName;
 | 
			
		||||
  if (
 | 
			
		||||
    _orings.some((item) => {
 | 
			
		||||
      return host.includes(item);
 | 
			
		||||
    })
 | 
			
		||||
  ) {
 | 
			
		||||
    res.setHeader('Access-Control-Allow-Origin', '*');
 | 
			
		||||
  }
 | 
			
		||||
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
 | 
			
		||||
 | 
			
		||||
  let user, app;
 | 
			
		||||
  let domainApp = false;
 | 
			
		||||
@@ -33,21 +46,19 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
 | 
			
		||||
    if (dns.hostName !== domain) {
 | 
			
		||||
      // redis获取域名对应的用户和应用
 | 
			
		||||
      domainApp = true;
 | 
			
		||||
      const key = 'domain:' + dns.hostName;
 | 
			
		||||
      const value = await redis.get(key);
 | 
			
		||||
      if (!value) {
 | 
			
		||||
      const data = await UserApp.getDomainApp(dns.hostName);
 | 
			
		||||
      if (!data) {
 | 
			
		||||
        res.writeHead(404, { 'Content-Type': 'text/plain' });
 | 
			
		||||
        res.write('Invalid domain\n');
 | 
			
		||||
        return res.end();
 | 
			
		||||
      }
 | 
			
		||||
      const [_user, _app] = value.split(':');
 | 
			
		||||
      if (!_user || !_app) {
 | 
			
		||||
      if (!data.user || !data.app) {
 | 
			
		||||
        res.writeHead(404, { 'Content-Type': 'text/plain' });
 | 
			
		||||
        res.write('Invalid domain, Config error\n');
 | 
			
		||||
        res.write('Invalid domain config\n');
 | 
			
		||||
        return res.end();
 | 
			
		||||
      }
 | 
			
		||||
      user = _user;
 | 
			
		||||
      app = _app;
 | 
			
		||||
      user = data.user;
 | 
			
		||||
      app = data.app;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const url = req.url;
 | 
			
		||||
@@ -145,8 +156,10 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
 | 
			
		||||
  const appFile = await userApp.getFile(appFileUrl);
 | 
			
		||||
  if (!appFile) {
 | 
			
		||||
    const [indexFilePath, etag] = indexFile.split('||');
 | 
			
		||||
    const contentType = getContentType(indexFilePath);
 | 
			
		||||
    const isHTML = contentType.includes('html');
 | 
			
		||||
    // 不存在的文件,返回indexFile的文件
 | 
			
		||||
    res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' });
 | 
			
		||||
    res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': isHTML ? 'no-cache' : 'public, max-age=3600' });
 | 
			
		||||
    const filePath = path.join(fileStore, indexFilePath);
 | 
			
		||||
    const readStream = fs.createReadStream(filePath);
 | 
			
		||||
    readStream.pipe(res);
 | 
			
		||||
@@ -161,9 +174,10 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
 | 
			
		||||
    }
 | 
			
		||||
    const filePath = path.join(fileStore, appFilePath);
 | 
			
		||||
    let contentType = getContentType(filePath);
 | 
			
		||||
    const isHTML = contentType.includes('html');
 | 
			
		||||
    res.writeHead(200, {
 | 
			
		||||
      'Content-Type': contentType,
 | 
			
		||||
      'Cache-Control': 'public, max-age=3600', // 设置缓存时间为 1 小时
 | 
			
		||||
      'Cache-Control': isHTML ? 'no-cache' : 'public, max-age=3600', // 设置缓存时间为 1 小时
 | 
			
		||||
      ETag: eTag,
 | 
			
		||||
    });
 | 
			
		||||
    const readStream = fs.createReadStream(filePath);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user