FrontController.OtcFormat 原使用 ResponseCache(Duration=600, Location=Any), 导致在 Admin 配置页保存 OtcFormat 后,前端最长 600s 内仍返回旧内容, 表现为'改了配置前端不起效果',需手工加 ?v= 才能破缓存。 改为 NoStore 后保存即对所有用户立即生效。 同时在 FrontController 类注释补充完整排查文档: - 三个同名 otcformat.js 文件的角色与路径 - DB AppConfig 真相源(PGroup=ProjectConfig, PName=OtcFormatConfig) - 写入/读取/启动三条数据流 - '前端不起效果'的 4 步排查顺序 - 多节点落盘只写当前节点的坑 OtcConfigController.AjaxSaveOtcFormat 补充多节点陷阱说明并指向 FrontController 文档。
322 lines
14 KiB
C#
322 lines
14 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using YieldChain.Helpers;
|
|
using YLErp.Configuration;
|
|
|
|
namespace YLErp.Web.Controllers
|
|
{
|
|
/// <summary>
|
|
/// 专用于输出前端JS的控制器
|
|
/// 数据必须为非敏感数据
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// ============================================================
|
|
/// OtcFormat 配置链路 & 排查说明(改前端格式化"不起效果"先看这里)
|
|
/// ============================================================
|
|
///
|
|
/// 【三个同名文件,角色不同】
|
|
/// 1. wwwroot/Scripts/init/otcformat.js —— 出厂默认值(兜底种子),Git 管理
|
|
/// 2. wwwroot/Scripts/base/otcformat.js —— 格式化引擎(运行时逻辑),Git 管理
|
|
/// 依赖 lodash.js / main.numberFormat / jQuery,且须先加载"配置段"再加载引擎
|
|
/// 3. App_Data/Config/otcformat.js —— 运行时配置文件(真正生效那份),非 Git 管理
|
|
/// 内容形如: var main = main || {}; main.formatOptions = { trading: {...} };
|
|
///
|
|
/// 【配置真相源:DB AppConfig 表】
|
|
/// PGroup = "ProjectConfig", PName = "OtcFormatConfig" (见 ConsAppConfig.OtcFormatConfig)
|
|
/// App_Data/Config/otcformat.js 只是 DB 配置落盘的镜像。
|
|
///
|
|
/// 【写入链路】OtcConfigController.AjaxSaveOtcFormat (Admin 配置页"保存"):
|
|
/// 表单 model → JsonConvert.SerializeObject
|
|
/// → AppConfigService.SaveOtcFormatConfig 写 DB
|
|
/// → File.WriteAllText(App_Data/Config/otcformat.js) 落盘(只写当前节点!)
|
|
/// → OtcFormatHelper.Initialize 刷新服务端内存
|
|
///
|
|
/// 【读取链路】FrontController.OtcFormat (前端 GET /front/otcformat):
|
|
/// 读 App_Data/Config/otcformat.js + Scripts/base/otcformat.js 拼接返回
|
|
/// 前端引擎 base/otcformat.js 用 main.formatOptions 覆盖内置默认值
|
|
///
|
|
/// 【应用启动】AppManager (subSystem==OtcWeb):
|
|
/// DB 有配置 → 反序列化进 OtcFormatHelper + 落盘 App_Data/Config/otcformat.js
|
|
/// DB 为空 → 用 Scripts/init/otcformat.js 兜底种子落盘
|
|
///
|
|
/// 【排查"前端不起效果"按此顺序】
|
|
/// ① 浏览器直访 /front/otcformat?v=随机数,看 main.formatOptions 是否最新
|
|
/// · 加 ?v= 正确 / 不加错误 → ResponseCache 缓存(本接口已改 NoStore,不应再出现)
|
|
/// · 加了 ?v= 仍错误 → 进 ②
|
|
/// ② 查 DB AppConfig(OtcFormatConfig).PValue 是否最新
|
|
/// · DB 空/旧 → AjaxSaveOtcFormat 没成功,查配置页 POST 响应
|
|
/// ③ 直接读服务器 App_Data/Config/otcformat.js 是否最新
|
|
/// · DB 对但文件旧 → AjaxSaveOtcFormat 的 File.WriteAllText 没生效,
|
|
/// 或多节点负载均衡下只写了接收 POST 的那台(读取命中了另一台旧文件)。
|
|
/// 读取和写入都只操作本机 Server.MapPath,无跨节点同步——多节点部署需注意。
|
|
/// ④ 页面引用方式:必须走 /front/otcformat,不能直接引 base/otcformat.js
|
|
/// (后者只有引擎默认值,没有配置段),例如 wwwroot/Scripts/test/tradeCalc.html 即如此。
|
|
/// ============================================================
|
|
/// </remarks>
|
|
[MyAuthorizeIgnore]
|
|
public class FrontController : BaseController
|
|
{
|
|
//基础数据(缓存60s)
|
|
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)]
|
|
public ActionResult BasicData(IEnumerable<string> types)
|
|
{
|
|
var js = new BasicDataJsBuilder(CurUser).Build(types);
|
|
return Content(js, "text/javascript");
|
|
}
|
|
|
|
//格式化选项(配置动态变更,禁用缓存——见下方 OtcFormat 排查说明)
|
|
[AllowAnonymous]
|
|
//重要:此处不能用 [ResponseCache(Duration=600,Location=Any)]。
|
|
// 原因:OtcFormat 是会动态变更的运行时配置(见 OtcConfigController.AjaxSaveOtcFormat),
|
|
// 若启用缓存,Admin 页保存后最长 600s 内仍返回旧内容,"前端改了配置不起效果"。
|
|
// 此前曾用 Duration=600,导致保存后必须等缓存过期或加 ?v=随机数 才生效。
|
|
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
|
|
public ActionResult OtcFormat()
|
|
{
|
|
var js = string.Empty;
|
|
var filePath = Server.MapPath("~/App_Data/Config/otcformat.js");
|
|
if (System.IO.File.Exists(filePath))
|
|
{
|
|
js = System.IO.File.ReadAllText(filePath);
|
|
}
|
|
filePath = Server.MapPath("~/Scripts/base/otcformat.js");
|
|
js += System.IO.File.ReadAllText(filePath);
|
|
return Content(js, "text/javascript");
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
|
|
public ActionResult SwapPricePrecision()
|
|
{
|
|
var filePath = Server.MapPath("~/App_Data/Config/swappriceprecision.js");
|
|
var js = System.IO.File.Exists(filePath)
|
|
? System.IO.File.ReadAllText(filePath)
|
|
: "var main = main || {}; main.swapPricePrecision = {};";
|
|
return Content(js, "text/javascript");
|
|
}
|
|
|
|
//今年及前后两年的日历数据(缓存120s)
|
|
[ResponseCache(Duration = 120, Location = ResponseCacheLocation.Any)]
|
|
public ActionResult Calendar()
|
|
{
|
|
//相信数据的准确性同时年份确定为4位数字
|
|
var dic = CalendarBLL.GetRecentCalendarModel().Select(n => new
|
|
{
|
|
key = n.Year.ToString()
|
|
+ ("chn".Equals(n.Country, StringComparison.OrdinalIgnoreCase) ? "" : n.Country?.ToLowerInvariant()),
|
|
val = string.IsNullOrWhiteSpace(n.HolidayJson)
|
|
? Enumerable.Empty<string>()
|
|
: Regex.Matches(n.HolidayJson, n.Year + @",\d\d,\d\d").Cast<Match>()
|
|
.Select(m => m.Value.Substring(5).Remove(2, 1))
|
|
.OrderBy(m => m).ToArray()
|
|
}).ToDictionary(n => n.key, n => n.val);
|
|
var json = JsonHelper.Serialize(dic);
|
|
var script = string.Concat("var ylotc=ylotc||{};ylotc.calendar=", json,
|
|
Properties.Resources.ylotc_calendar);
|
|
return Content(script, "text/javascript");
|
|
}
|
|
|
|
[ResponseCache(Duration = 30, Location = ResponseCacheLocation.Any)]
|
|
public ActionResult ClientEditConfig()
|
|
{
|
|
var json = Modules.SystemModule.ClientEditConfigService.GetConfigJson();
|
|
return Content(json, "text/javascript");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 基础数据构建器
|
|
/// </summary>
|
|
class BasicDataJsBuilder : BasicViewModel
|
|
{
|
|
const string ylotcDefine = "var ylotc=ylotc||{};ylotc.$basicData=1;";
|
|
|
|
StringBuilder _sb;
|
|
|
|
public BasicDataJsBuilder(UserInfo userInfo) : base(userInfo)
|
|
{
|
|
|
|
}
|
|
|
|
public string Build(IEnumerable<string> types)
|
|
{
|
|
if (types == null || !types.Any())
|
|
{
|
|
return ylotcDefine;
|
|
}
|
|
|
|
_sb = new StringBuilder(300);
|
|
_sb.AppendLine(ylotcDefine);
|
|
|
|
foreach (var type in types)
|
|
{
|
|
switch (type)
|
|
{
|
|
case "簿记":
|
|
case "簿记账户":
|
|
case nameof(AssetUnit):
|
|
AssetUnit();
|
|
break;
|
|
case "簿记ALL":
|
|
case "簿记账户ALL":
|
|
case nameof(AssetUnitALL):
|
|
AssetUnitALL();
|
|
break;
|
|
case "簿记账户组":
|
|
AssetUnitGroup();
|
|
break;
|
|
case "客户":
|
|
case "客户信息":
|
|
case nameof(DBModels.Client):
|
|
Client();
|
|
break;
|
|
case "标的":
|
|
case "标的信息":
|
|
case nameof(DBModels.underlying_manager):
|
|
UnderlyingManager(false, false);
|
|
break;
|
|
case "标的Live":
|
|
case "标的信息Live":
|
|
UnderlyingManager(true, false);
|
|
break;
|
|
case "权益标的":
|
|
case "权益标的信息":
|
|
UnderlyingManager(false, true);
|
|
break;
|
|
case "权益标的Live":
|
|
case "权益标的信息Live":
|
|
UnderlyingManager(true, true);
|
|
break;
|
|
case "广发商贸标的信息":
|
|
GFUnderlyingManager(true, false);
|
|
break;
|
|
case "品种":
|
|
case "标的品种":
|
|
case nameof(Variety):
|
|
Variety();
|
|
break;
|
|
case "品种类型":
|
|
VarietyTypes();
|
|
break;
|
|
case "标的板块":
|
|
case "板块性质":
|
|
UnderlyingBlocks();
|
|
break;
|
|
case "交易员":
|
|
Traders();
|
|
break;
|
|
case "系统用户":
|
|
SystemUsers();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return _sb.ToString();
|
|
}
|
|
|
|
// 簿记账户
|
|
private void AssetUnit()
|
|
{
|
|
var datas = JsDataModel.GetAssetUnits(CurUser);
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.assetunits=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
// 簿记账户(所有)
|
|
private void AssetUnitALL()
|
|
{
|
|
var datas = JsDataModel.GetAllAssetUnits();
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.assetunits=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
// 簿记账户组
|
|
private void AssetUnitGroup()
|
|
{
|
|
var datas = JsDataModel.GetAssetUnitGroups();
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.assetunitgroups=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
// 客户信息
|
|
private void Client()
|
|
{
|
|
var datas = JsDataModel.GetClients();
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.clients=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
// 标的信息
|
|
private void UnderlyingManager(bool onlyLive, bool onlyEquity)
|
|
{
|
|
var datas = JsDataModel.GetUnderlyings(onlyLive, onlyEquity);
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
|
|
_sb.Append("ylotc.underlyings=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
private void GFUnderlyingManager(bool onlyLive, bool onlyEquity)
|
|
{
|
|
var datas = JsDataModel.GetGFUnderlyings(onlyLive, onlyEquity);
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
|
|
_sb.Append("ylotc.gfunderlyings=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
//标的品种
|
|
private void Variety()
|
|
{
|
|
var datas = JsDataModel.GetVarieties(CurUser);
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.varieties=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 标的品种类型
|
|
/// </summary>
|
|
private void VarietyTypes()
|
|
{
|
|
var datas = JsDataModel.GetVarietyTypes();
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.varietyGroups=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 标的板块性质
|
|
/// </summary>
|
|
private void UnderlyingBlocks()
|
|
{
|
|
var datas = JsDataModel.GetUnderlyingBlocks();
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.underlyingBlocks=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 交易员
|
|
/// </summary>
|
|
private void Traders()
|
|
{
|
|
var traders = UserBLL.GetUsersByPosition();
|
|
var datas = traders.Select(n => new { id = n.Id, n.Name, n.UserGroup, PinYin = PingYinHelper.GetFirstPinYin(n.Name) }).ToArray();
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.traders=").Append(dataJson).AppendLine(";");
|
|
}
|
|
/// <summary>
|
|
/// 系统所有用户
|
|
/// </summary>
|
|
private void SystemUsers()
|
|
{
|
|
var systemUsers = UserBLL.GetSystemsByUserId();
|
|
var datas = systemUsers.Select(n => new { id = n.Id, n.Name, n.UserGroup, PinYin = PingYinHelper.GetFirstPinYin(n.Name) }).ToArray();
|
|
var dataJson = JsonHelper.Serialize(datas);
|
|
_sb.Append("ylotc.sysusers=").Append(dataJson).AppendLine(";");
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return _sb.ToString();
|
|
}
|
|
}
|
|
}
|
|
}
|