交互模式直接运行
大石头 authored at 2024-11-25 09:58:04
6.86 KiB
Stardust
using Microsoft.AspNetCore.Mvc;
using NewLife;
using NewLife.Remoting.Extensions;
using Stardust.Data;
using Stardust.Data.Configs;
using Stardust.Models;
using Stardust.Server.Models;
using Stardust.Server.Services;
using TokenService = Stardust.Server.Services.TokenService;

namespace Stardust.Server.Controllers;

/// <summary>配置中心服务。向应用提供配置服务</summary>
[Route("[controller]/[action]")]
public class ConfigController : ControllerBase
{
    private readonly ConfigService _configService;
    private readonly TokenService _tokenService;
    private readonly AppOnlineService _appOnline;
    private readonly StarServerSetting _setting;

    public ConfigController(ConfigService configService, TokenService tokenService, AppOnlineService appOnline, StarServerSetting setting)
    {
        _configService = configService;
        _tokenService = tokenService;
        _appOnline = appOnline;
        _setting = setting;
    }

    [ApiFilter]
    public ConfigInfo GetAll(String appId, String secret, String clientId, String token, String scope, Int32 version)
    {
        if (appId.IsNullOrEmpty() && token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(appId));

        // 验证
        var (app, online) = Valid(appId, secret, clientId, token);
        var ip = HttpContext.GetUserHost();

        // 作用域为空时重写
        scope = scope.IsNullOrEmpty() ? AppRule.CheckScope(app.Id, ip, clientId) : scope;

        // 作用域有改变时,也要返回配置数据
        var change = online.Scope != scope;
        online.Scope = scope;
        online.SaveAsync(3_000);

        // 版本没有变化时,不做计算处理,不返回配置数据
        if (!change && version > 0 && version >= app.Version) return new ConfigInfo { Version = app.Version, UpdateTime = app.UpdateTime };
        var dic = _configService.GetConfigs(app, scope);

        // 返回WorkerId
        if (app.EnableWorkerId && dic.ContainsKey(_configService.WorkerIdName))
            dic[_configService.WorkerIdName] = online.WorkerId + "";

        return new ConfigInfo
        {
            Version = app.Version,
            Scope = scope,
            SourceIP = ip,
            NextVersion = app.NextVersion,
            NextPublish = app.PublishTime.ToFullString(""),
            UpdateTime = app.UpdateTime,
            Configs = dic,
        };
    }

    [ApiFilter]
    [HttpPost]
    public ConfigInfo GetAll([FromBody] ConfigInModel model, String token)
    {
        if (model.AppId.IsNullOrEmpty() && token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.AppId));

        // 验证
        var (app, online) = Valid(model.AppId, model.Secret, model.ClientId, token);
        var ip = HttpContext.GetUserHost();

        // 使用键和缺失键
        if (!model.UsedKeys.IsNullOrEmpty()) app.UsedKeys = model.UsedKeys;
        if (!model.MissedKeys.IsNullOrEmpty()) app.MissedKeys = model.MissedKeys;
        app.Update();

        // 作用域为空时重写
        var scope = model.Scope;
        scope = scope.IsNullOrEmpty() ? AppRule.CheckScope(app.Id, ip, model.ClientId) : scope;

        // 作用域有改变时,也要返回配置数据
        var change = online.Scope != scope;
        online.Scope = scope;
        online.SaveAsync(3_000);

        // 版本没有变化时,不做计算处理,不返回配置数据
        if (!change && model.Version > 0 && model.Version >= app.Version)
            return new ConfigInfo
            {
                Version = app.Version,
                Scope = scope,
                SourceIP = ip,
                NextVersion = app.NextVersion,
                NextPublish = app.PublishTime.ToFullString(""),
                UpdateTime = app.UpdateTime
            };

        var dic = _configService.GetConfigs(app, scope);

        // 返回WorkerId
        if (app.EnableWorkerId && dic.ContainsKey(_configService.WorkerIdName))
            dic[_configService.WorkerIdName] = online.WorkerId + "";

        return new ConfigInfo
        {
            Version = app.Version,
            Scope = scope,
            SourceIP = ip,
            NextVersion = app.NextVersion,
            NextPublish = app.PublishTime.ToFullString(""),
            UpdateTime = app.UpdateTime,
            Configs = dic,
        };
    }

    private (AppConfig, AppOnline) Valid(String appId, String secret, String clientId, String token)
    {
        // 优先令牌解码
        App app = null;
        if (!token.IsNullOrEmpty())
        {
            var (jwt, ap1) = _tokenService.DecodeToken(token, _setting.TokenSecret);
            if (appId.IsNullOrEmpty()) appId = ap1?.Name;
            if (clientId.IsNullOrEmpty()) clientId = jwt.Id;

            app = ap1;
        }

        var ip = HttpContext.GetUserHost();
        app ??= _tokenService.Authorize(appId, secret, _setting.AppAutoRegister, ip);

        // 新建应用配置
        var config = AppConfig.FindByName(appId);
        config ??= AppConfig.Find(AppConfig._.Name == appId);
        if (config == null)
        {
            var obj = AppConfig.Meta.Table;
            lock (obj)
            {
                config = AppConfig.FindByName(appId);
                if (config == null)
                {
                    config = new AppConfig
                    {
                        Name = app.Name,
                        AppId = app.Id,
                        Enable = app.Enable,
                    };

                    config.Insert();
                }
            }
        }

        if (app != null)
        {
            // 双向同步应用分类
            if (!app.Category.IsNullOrEmpty())
                config.Category = app.Category;
            else if (!config.Category.IsNullOrEmpty())
            {
                app.Category = config.Category;
                app.Update();
            }

            if (config.AppId == 0) config.AppId = app.Id;
            config.Update();
        }

        //var ip = HttpContext.GetUserHost();
        if (clientId.IsNullOrEmpty()) clientId = ip;

        // 更新心跳信息
        var online = _appOnline.UpdateOnline(app, clientId, ip, token);

        // 检查应用有效性
        if (!config.Enable) throw new ArgumentOutOfRangeException(nameof(appId), $"应用[{appId}]已禁用!");

        // 刷新WorkerId
        if (config.EnableWorkerId) _configService.RefreshWorkerId(config, online);

        return (config, online);
    }

    [ApiFilter]
    [HttpPost]
    public Int32 SetAll([FromBody] SetConfigModel model, String token)
    {
        if (model.AppId.IsNullOrEmpty() && token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.AppId));

        // 验证
        var (app, _) = Valid(model.AppId, model.Secret, model.ClientId, token);
        if (app.Readonly) throw new Exception($"应用[{app}]处于只读模式,禁止修改");

        return _configService.SetConfigs(app, model.Configs);
    }
}