修正异常:The requested operation requires an element of type 'Object', but the target element has type 'String'.
大石头 authored at 2021-12-03 23:15:10
7.68 KiB
Stardust
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using NewLife;
using NewLife.Log;
using NewLife.Threading;
using Stardust.Data.Configs;

namespace Stardust.Server.Services
{
    public class ConfigService
    {
        private TimerX _timer;
        private readonly ITracer _tracer;

        public ConfigService(ITracer tracer)
        {
            _tracer = tracer;

            StartTimer();
        }

        /// <summary>为应用解析指定键的值,处理内嵌</summary>
        /// <param name="app"></param>
        /// <param name="cfg"></param>
        /// <param name="scope"></param>
        /// <returns></returns>
        public String Resolve(AppConfig app, ConfigData cfg, String scope)
        {
            var value = cfg?.Value;
            if (value.IsNullOrEmpty()) return value;

            // 要求内嵌全部解析
            var p = 0;
            while (true)
            {
                var p1 = value.IndexOf("${", p);
                if (p1 < 0) break;

                var p2 = value.IndexOf('}', p1 + 2);
                if (p2 < 0) break;

                // 替换
                var item = value.Substring(p1 + 2, p2 - p1 - 2);
                // 拆分 ${key@app:scope}
                var ss = item.Split("@", ":");
                var key2 = ss[0];
                var app2 = ss.Length > 1 ? ss[1] : "";
                var scope2 = ss.Length > 2 ? ss[2] : "";

                //item = replace(key, app, scope) + "";
                {
                    var ap2 = AppConfig.FindByName(app2);
                    var scope3 = !scope2.IsNullOrEmpty() ? scope2 : scope;
                    var cfg2 = ConfigData.Acquire(ap2 ?? app, key2, scope3);
                    if (cfg2 == null) throw new Exception($"在应用[{app}]的[{cfg.Key}]中无法解析[{item}]");

                    item = Resolve(ap2 ?? app, cfg2, scope3);
                }

                // 重新组合
                var left = value[..p1];
                var right = value[(p2 + 1)..];
                value = left + item + right;

                // 移动游标,加速下一次处理
                p = left.Length + item.Length;
            }

            return value;
        }

        public void StartTimer()
        {
            if (_timer == null)
            {
                lock (this)
                {
                    if (_timer == null) _timer = new TimerX(DoConfigWork, null, 15_000, 60_000) { Async = true };
                }
            }
        }

        private void DoConfigWork(Object state)
        {
            var list = AppConfig.FindAll();
            var next = DateTime.MinValue;
            foreach (var item in list)
            {
                if (!item.Enable || item.PublishTime.Year < 2000) continue;

                using var span = _tracer?.NewSpan("AutoPublish", item);

                // 时间到了,发布,或者计算最近一个到期应用
                if (item.PublishTime <= DateTime.Now)
                    item.Publish();
                else if (item.PublishTime < next || next.Year < 2000)
                    next = item.PublishTime;
            }

            // 如果下一个到期应用时间很短,主动调整定时器
            if (next.Year > 2000)
            {
                var ts = next - DateTime.Now;
                if (ts.TotalMilliseconds < _timer.Period) _timer.SetNext((Int32)ts.TotalMilliseconds);
            }
        }

        public IDictionary<String, String> GetConfigs(AppConfig app, String scope)
        {
            using var span = _tracer?.NewSpan(nameof(GetConfigs), $"{app} {scope}");

            var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);

            var list = app.Configs;
            list = ConfigData.SelectScope(list, scope);

            // 本应用
            foreach (var cfg in list)
            {
                // 跳过内部
                if (cfg.Key.IsNullOrEmpty() || cfg.Key[0] == '_') continue;

                // 为该key选择最合适的值,解析内嵌
                dic[cfg.Key] = Resolve(app, cfg, scope);
            }

            // 共享应用
            var qs = app.GetQuotes();
            foreach (var item in qs)
            {
                var list2 = item.Configs;
                list2 = ConfigData.SelectScope(list2, scope);
                foreach (var cfg in list2)
                {
                    // 跳过内部
                    if (cfg.Key.IsNullOrEmpty() || cfg.Key[0] == '_') continue;

                    // 仅添加还没有的键
                    if (dic.ContainsKey(cfg.Key)) continue;

                    // 为该key选择最合适的值,解析内嵌
                    dic[cfg.Key] = Resolve(item, cfg, scope);
                }
            }

            // 全局应用
            foreach (var item in AppConfig.GetValids().Where(e => e.IsGlobal))
            {
                var list2 = item.Configs;
                list2 = ConfigData.SelectScope(list2, scope);
                foreach (var cfg in list2)
                {
                    // 跳过内部
                    if (cfg.Key.IsNullOrEmpty() || cfg.Key[0] == '_') continue;

                    // 仅添加还没有的键
                    if (dic.ContainsKey(cfg.Key)) continue;

                    // 为该key选择最合适的值,解析内嵌
                    dic[cfg.Key] = Resolve(item, cfg, scope);
                }
            }

            return dic;
        }

        public Int32 SetConfigs(AppConfig app, IDictionary<String, Object> configs)
        {
            var ver = app.AcquireNewVersion();

            // 开启事务,整体完成
            using var tran = ConfigData.Meta.CreateTrans();

            var list = ConfigData.FindAllByApp(app.Id);

            // 逐行插入数据,不能覆盖已存在数据
            foreach (var item in configs)
            {
                var data = list.FirstOrDefault(e => e.Key.EqualIgnoreCase(item.Key));
                if (data == null)
                {
                    data = new ConfigData
                    {
                        AppId = app.Id,
                        Key = item.Key,
                        //Value = item.Value + "",
                        Version = ver,
                        Enable = true,
                    };

                    if (item.Value is IDictionary<String, Object> dic)
                    {
                        if (dic.TryGetValue("Value", out var v)) data.Value = v + "";
                        if (dic.TryGetValue("Comment", out v)) data.Remark = v + "";
                    }
                    else if (item.Value is JsonElement json && json.ValueKind == JsonValueKind.Object)
                    {
                        if (json.TryGetProperty("Value", out var v)) data.Value = v + "";
                        if (json.TryGetProperty("Comment", out v)) data.Remark = v + "";
                    }
                    else
                        data.Value = item.Value + "";

                    data.Insert();
                }
                else
                {
                    if (item.Value is IDictionary<String, Object> dic)
                    {
                        if (dic.TryGetValue("Comment", out var v)) data.Remark = v + "";
                    }
                    else if (item.Value is JsonElement json && json.ValueKind == JsonValueKind.Object)
                    {
                        if (json.TryGetProperty("Comment", out var v)) data.Remark = v + "";
                    }

                    data.Update();
                }
            }

            //// 整体发布
            //app.Publish();

            tran.Commit();

            return configs.Count;
        }
    }
}