Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled

Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
import axios from 'axios';
/**
* I18n client for translation and localization
* Used by other services to get translations and format values
*/
export class I18nClient {
constructor(config = {}) {
this.baseURL = config.baseURL || process.env.I18N_SERVICE_URL || 'http://localhost:3011';
this.timeout = config.timeout || 30000;
this.apiVersion = config.apiVersion || 'v1';
this.defaultLanguage = config.defaultLanguage || 'en';
this.cache = new Map();
this.cacheTimeout = config.cacheTimeout || 300000; // 5 minutes
this.client = axios.create({
baseURL: `${this.baseURL}/api/${this.apiVersion}`,
timeout: this.timeout,
headers: {
'Content-Type': 'application/json'
}
});
// Add request interceptor for internal service header
this.client.interceptors.request.use(config => {
config.headers['X-Internal-Service'] = process.env.SERVICE_NAME || 'unknown';
return config;
});
}
/**
* Get single translation
*/
async translate(key, language = this.defaultLanguage, options = {}) {
const cacheKey = `${language}:${key}:${options.namespace || 'translation'}`;
// Check cache
const cached = this.getFromCache(cacheKey);
if (cached) return cached;
try {
const response = await this.client.get('/translations', {
params: {
key,
language,
namespace: options.namespace || 'translation'
}
});
const value = response.data.translation.value;
this.setCache(cacheKey, value);
return this.interpolate(value, options.variables || {});
} catch (error) {
console.error('Translation error:', error);
return options.defaultValue || key;
}
}
/**
* Get multiple translations
*/
async translateBatch(keys, language = this.defaultLanguage, options = {}) {
try {
const response = await this.client.get('/translations', {
params: {
keys: keys.join(','),
language,
namespace: options.namespace || 'translation'
}
});
return response.data.translations;
} catch (error) {
console.error('Batch translation error:', error);
// Return keys as fallback
return keys.reduce((acc, key) => {
acc[key] = key;
return acc;
}, {});
}
}
/**
* Detect user's language
*/
async detectLanguage(headers = {}) {
try {
const response = await this.client.get('/languages/detect', {
headers: {
'accept-language': headers['accept-language'],
'cf-ipcountry': headers['cf-ipcountry']
}
});
return response.data.language;
} catch (error) {
console.error('Language detection error:', error);
return this.defaultLanguage;
}
}
/**
* Get enabled languages
*/
async getLanguages() {
const cacheKey = 'enabled-languages';
const cached = this.getFromCache(cacheKey);
if (cached) return cached;
try {
const response = await this.client.get('/languages/enabled');
const languages = response.data.languages;
this.setCache(cacheKey, languages, 3600000); // Cache for 1 hour
return languages;
} catch (error) {
console.error('Get languages error:', error);
return [];
}
}
/**
* Format number
*/
async formatNumber(number, language = this.defaultLanguage, options = {}) {
try {
const response = await this.client.post('/localization/format/number', {
number,
language,
options
});
return response.data.formatted;
} catch (error) {
console.error('Number formatting error:', error);
return number.toString();
}
}
/**
* Format currency
*/
async formatCurrency(amount, language = this.defaultLanguage, currency = 'USD') {
try {
const response = await this.client.post('/localization/format/currency', {
amount,
language,
currency
});
return response.data.formatted;
} catch (error) {
console.error('Currency formatting error:', error);
return `${currency} ${amount}`;
}
}
/**
* Format date
*/
async formatDate(date, language = this.defaultLanguage, options = {}) {
try {
const response = await this.client.post('/localization/format/date', {
date,
language,
options
});
return response.data.formatted;
} catch (error) {
console.error('Date formatting error:', error);
return new Date(date).toLocaleString();
}
}
/**
* Format relative time
*/
async formatRelativeTime(date, language = this.defaultLanguage) {
try {
const response = await this.client.post('/localization/format/relative-time', {
date,
language
});
return response.data.formatted;
} catch (error) {
console.error('Relative time formatting error:', error);
return date;
}
}
/**
* Get text direction
*/
async getTextDirection(language = this.defaultLanguage) {
const cacheKey = `direction:${language}`;
const cached = this.getFromCache(cacheKey);
if (cached) return cached;
try {
const response = await this.client.get(`/localization/direction/${language}`);
const direction = response.data.direction;
this.setCache(cacheKey, direction, 3600000); // Cache for 1 hour
return direction;
} catch (error) {
console.error('Get text direction error:', error);
return 'ltr';
}
}
/**
* Create i18n middleware for Express
*/
middleware() {
return async (req, res, next) => {
// Detect language
req.language = await this.detectLanguage(req.headers);
// Add translation function
req.t = (key, options = {}) => {
return this.translate(key, req.language, options);
};
// Add formatting functions
req.formatNumber = (number, options) => {
return this.formatNumber(number, req.language, options);
};
req.formatCurrency = (amount, currency) => {
return this.formatCurrency(amount, req.language, currency);
};
req.formatDate = (date, options) => {
return this.formatDate(date, req.language, options);
};
// Set response language header
res.setHeader('Content-Language', req.language);
next();
};
}
/**
* Interpolate variables in text
*/
interpolate(text, variables = {}) {
return text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return variables[key] !== undefined ? variables[key] : match;
});
}
/**
* Cache management
*/
getFromCache(key) {
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() > cached.expires) {
this.cache.delete(key);
return null;
}
return cached.value;
}
setCache(key, value, timeout = this.cacheTimeout) {
this.cache.set(key, {
value,
expires: Date.now() + timeout
});
}
clearCache() {
this.cache.clear();
}
}
// Singleton instance
let defaultClient = null;
export const getI18nClient = (config) => {
if (!defaultClient) {
defaultClient = new I18nClient(config);
}
return defaultClient;
};
// Direct exports for convenience
export const translate = async (key, language, options) => {
const client = getI18nClient();
return client.translate(key, language, options);
};
export const detectLanguage = async (headers) => {
const client = getI18nClient();
return client.detectLanguage(headers);
};

View File

@@ -0,0 +1,232 @@
import axios from 'axios';
/**
* Template client for rendering message templates
* Used by other services to render templates before sending messages
*/
export class TemplateClient {
constructor(config = {}) {
this.baseURL = config.baseURL || process.env.TEMPLATE_SERVICE_URL || 'http://localhost:3010';
this.timeout = config.timeout || 30000;
this.apiVersion = config.apiVersion || 'v1';
this.authToken = config.authToken;
this.client = axios.create({
baseURL: `${this.baseURL}/api/${this.apiVersion}`,
timeout: this.timeout,
headers: {
'Content-Type': 'application/json'
}
});
// Add auth header if token provided
if (this.authToken) {
this.client.defaults.headers['Authorization'] = `Bearer ${this.authToken}`;
}
// Add request interceptor for internal service header
this.client.interceptors.request.use(config => {
config.headers['X-Internal-Service'] = process.env.SERVICE_NAME || 'unknown';
return config;
});
}
/**
* Render a template with data
*/
async renderTemplate(templateId, data = {}, options = {}) {
try {
const response = await this.client.post('/templates/render', {
templateId,
data,
preview: options.preview || false,
format: options.format // Optional: override template format
});
return {
success: true,
content: response.data.content,
format: response.data.format,
metadata: response.data.metadata
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message,
details: error.response?.data?.details
};
}
}
/**
* Render a template by name and language
*/
async renderTemplateByName(name, language, data = {}, options = {}) {
try {
const response = await this.client.post('/templates/render-by-name', {
name,
language,
data,
preview: options.preview || false
});
return {
success: true,
content: response.data.content,
format: response.data.format,
metadata: response.data.metadata
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message,
details: error.response?.data?.details
};
}
}
/**
* Get a template by ID (for caching or local rendering)
*/
async getTemplate(templateId) {
try {
const response = await this.client.get(`/templates/${templateId}`);
return {
success: true,
template: response.data.template
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message
};
}
}
/**
* Get all templates for a category
*/
async getTemplatesByCategory(categoryId) {
try {
const response = await this.client.get('/templates', {
params: { category: categoryId }
});
return {
success: true,
templates: response.data.templates
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message
};
}
}
/**
* Validate template data before rendering
*/
async validateTemplateData(templateId, data) {
try {
const response = await this.client.post('/templates/validate-data', {
templateId,
data
});
return {
success: true,
valid: response.data.valid,
errors: response.data.errors || []
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message
};
}
}
/**
* Get available variables for a template category
*/
async getCategoryVariables(category) {
try {
const response = await this.client.get('/template-variables/available', {
params: { category }
});
return {
success: true,
variables: response.data.variables
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message
};
}
}
/**
* Batch render multiple templates
*/
async batchRenderTemplates(requests) {
try {
const response = await this.client.post('/templates/batch-render', {
requests
});
return {
success: true,
results: response.data.results
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message
};
}
}
/**
* Get sample data for testing templates
*/
async getSampleData(category) {
try {
const response = await this.client.get('/template-variables/sample-data', {
params: { category }
});
return {
success: true,
sampleData: response.data.sampleData
};
} catch (error) {
return {
success: false,
error: error.response?.data?.error || error.message
};
}
}
}
// Singleton instance for easy import
let defaultClient = null;
export const getTemplateClient = (config) => {
if (!defaultClient) {
defaultClient = new TemplateClient(config);
}
return defaultClient;
};
// Direct exports for convenience
export const renderTemplate = async (templateId, data, options) => {
const client = getTemplateClient();
return client.renderTemplate(templateId, data, options);
};
export const renderTemplateByName = async (name, language, data, options) => {
const client = getTemplateClient();
return client.renderTemplateByName(name, language, data, options);
};