复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
@@ -0,0 +1,20 @@
#include "SystemController.h"
using json = nlohmann::json;
void SystemController::GetHealth(
const HttpRequestPtr&,
std::function<void(const HttpResponsePtr&)>&& callback) {
json data;
data["status"] = "ok";
SendSuccess(callback, data);
}
void SystemController::GetVersion(
const HttpRequestPtr&,
std::function<void(const HttpResponsePtr&)>&& callback) {
json data;
data["name"] = "wind_power_cal";
data["version"] = "0.1.0";
SendSuccess(callback, data);
}
@@ -0,0 +1,23 @@
#ifndef SYSTEMCONTROLLER_H
#define SYSTEMCONTROLLER_H
#include <drogon/HttpController.h>
#include <nlohmann/json.hpp>
#include "utils/ResponseUtil.h"
using namespace drogon;
class SystemController : public drogon::HttpController<SystemController> {
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(SystemController::GetHealth, "/api/system/health", Get);
ADD_METHOD_TO(SystemController::GetVersion, "/api/system/version", Get);
METHOD_LIST_END
void GetHealth(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback);
void GetVersion(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback);
};
#endif
@@ -0,0 +1,758 @@
#include "WindPowerController.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <optional>
#include <random>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
using json = nlohmann::json;
namespace fs = std::filesystem;
namespace {
constexpr int kErrorInvalidRequest = 1001;
constexpr int kErrorJobNotFound = 1002;
constexpr int kErrorServer = 1003;
struct RawRow {
std::string time;
std::string fan_id;
double wind_speed = 0.0;
double active_power = 0.0;
double generator_speed = 0.0;
};
struct ValidRow {
std::string time;
std::time_t timestamp = 0;
std::string date;
std::string fan_id;
double wind_speed = 0.0;
double active_power = 0.0;
double generator_speed = 0.0;
double tip_speed_ratio = 0.0;
};
struct RemovedPoint {
ValidRow row;
std::string reason;
};
struct CalculationOptions {
double rated_power = 4800.0;
double rated_wind_speed = 18.0;
double power_step = 5.0;
double cleaning_wind_speed_step = 0.25;
double curve_wind_speed_step = 0.5;
double wind_speed_change_threshold = 1.0;
double iqr_lower_multiplier = 1.8;
double iqr_upper_multiplier = 2.0;
double minimum_generator_speed = 1.0;
double generator_speed_k = 0.9;
};
std::string Trim(const std::string& value) {
const auto begin = value.find_first_not_of(" \t\r\n");
if (begin == std::string::npos) {
return "";
}
const auto end = value.find_last_not_of(" \t\r\n");
return value.substr(begin, end - begin + 1);
}
bool IsSafeJobId(const std::string& job_id) {
if (job_id.empty() || job_id.size() > 80) {
return false;
}
return std::all_of(job_id.begin(), job_id.end(), [](unsigned char ch) {
return std::isalnum(ch) || ch == '_' || ch == '-';
});
}
fs::path JobsRoot() {
return fs::path("uploads") / "wind_jobs";
}
fs::path JobDir(const std::string& job_id) {
return JobsRoot() / job_id;
}
fs::path JobRowsPath(const std::string& job_id) {
return JobDir(job_id) / "rows.jsonl";
}
std::string GenerateJobId() {
const auto now = std::chrono::system_clock::now().time_since_epoch().count();
std::random_device rd;
std::mt19937 rng(rd());
std::uniform_int_distribution<int> dist(0, 15);
std::ostringstream oss;
oss << "job_" << now << "_";
for (int i = 0; i < 8; ++i) {
oss << std::hex << dist(rng);
}
return oss.str();
}
std::optional<json> ParseBody(const HttpRequestPtr& req, std::string& error) {
try {
if (req->getBody().empty()) {
error = "请求体不能为空";
return std::nullopt;
}
return json::parse(req->getBody());
} catch (const std::exception&) {
error = "请求体不是合法 JSON";
return std::nullopt;
}
}
std::optional<std::string> GetStringField(const json& body, const std::string& field) {
if (!body.contains(field)) {
return std::nullopt;
}
if (body[field].is_string()) {
return Trim(body[field].get<std::string>());
}
if (body[field].is_number_integer()) {
return std::to_string(body[field].get<long long>());
}
if (body[field].is_number_float()) {
std::ostringstream oss;
oss << body[field].get<double>();
return oss.str();
}
return std::nullopt;
}
std::optional<double> GetDoubleField(const json& body, const std::string& field) {
if (!body.contains(field)) {
return std::nullopt;
}
if (body[field].is_number()) {
return body[field].get<double>();
}
if (body[field].is_string()) {
try {
size_t parsed = 0;
const auto value = std::stod(Trim(body[field].get<std::string>()), &parsed);
if (parsed == Trim(body[field].get<std::string>()).size()) {
return value;
}
} catch (const std::exception&) {
return std::nullopt;
}
}
return std::nullopt;
}
std::optional<std::time_t> ParseTime(std::string value) {
value = Trim(value);
if (value.empty()) {
return std::nullopt;
}
std::replace(value.begin(), value.end(), 'T', ' ');
if (!value.empty() && value.back() == 'Z') {
value.pop_back();
}
const auto dot_pos = value.find('.');
if (dot_pos != std::string::npos) {
value = value.substr(0, dot_pos);
}
if (value.size() == 10) {
value += " 00:00:00";
}
std::tm tm = {};
std::istringstream iss(value);
iss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
if (iss.fail()) {
return std::nullopt;
}
tm.tm_isdst = -1;
return std::mktime(&tm);
}
json CounterJson(const std::unordered_map<std::string, int>& counters) {
json data = json::object();
for (const auto& item : counters) {
data[item.first] = item.second;
}
return data;
}
double Quantile(std::vector<double> values, double q) {
if (values.empty()) {
return 0.0;
}
std::sort(values.begin(), values.end());
const double pos = (static_cast<double>(values.size()) - 1.0) * q;
const auto low = static_cast<size_t>(std::floor(pos));
const auto high = static_cast<size_t>(std::ceil(pos));
if (low == high) {
return values[low];
}
const double weight = pos - static_cast<double>(low);
return values[low] * (1.0 - weight) + values[high] * weight;
}
double Mean(const std::vector<double>& values) {
if (values.empty()) {
return 0.0;
}
double sum = 0.0;
for (double value : values) {
sum += value;
}
return sum / static_cast<double>(values.size());
}
double StdDev(const std::vector<double>& values, double mean) {
if (values.size() < 2) {
return 0.0;
}
double sum = 0.0;
for (double value : values) {
const double diff = value - mean;
sum += diff * diff;
}
return std::sqrt(sum / static_cast<double>(values.size()));
}
CalculationOptions ParseOptions(const json& body) {
CalculationOptions options;
if (!body.contains("options") || !body["options"].is_object()) {
return options;
}
const auto& opt = body["options"];
if (const auto value = GetDoubleField(opt, "rated_power");
value.has_value() && value.value() > 0.0) {
options.rated_power = value.value();
}
if (const auto value = GetDoubleField(opt, "rated_wind_speed");
value.has_value() && value.value() > 0.0) {
options.rated_wind_speed = value.value();
}
if (const auto value = GetDoubleField(opt, "power_step");
value.has_value() && value.value() > 0.0) {
options.power_step = value.value();
}
if (const auto value = GetDoubleField(opt, "cleaning_wind_speed_step");
value.has_value() && value.value() > 0.0 && value.value() <= 2.0) {
options.cleaning_wind_speed_step = value.value();
}
if (const auto value = GetDoubleField(opt, "curve_wind_speed_step");
value.has_value() && value.value() > 0.0 && value.value() <= 2.0) {
options.curve_wind_speed_step = value.value();
}
if (const auto value = GetDoubleField(opt, "wind_speed_change_threshold");
value.has_value() && value.value() >= 0.0) {
options.wind_speed_change_threshold = value.value();
}
if (const auto value = GetDoubleField(opt, "iqr_lower_multiplier");
value.has_value() && value.value() >= 0.0 && value.value() <= 10.0) {
options.iqr_lower_multiplier = value.value();
}
if (const auto value = GetDoubleField(opt, "iqr_upper_multiplier");
value.has_value() && value.value() >= 0.0 && value.value() <= 10.0) {
options.iqr_upper_multiplier = value.value();
}
if (const auto value = GetDoubleField(opt, "minimum_generator_speed");
value.has_value() && value.value() >= 0.0) {
options.minimum_generator_speed = value.value();
}
if (const auto value = GetDoubleField(opt, "generator_speed_k");
value.has_value() && value.value() >= 0.0) {
options.generator_speed_k = value.value();
}
return options;
}
void AddInvalid(std::unordered_map<std::string, int>& counters, const std::string& reason) {
counters[reason] += 1;
}
std::vector<ValidRow> FilterLimitPower(const std::vector<ValidRow>& rows,
const CalculationOptions& options,
int& removed_count,
std::vector<RemovedPoint>& removed_points) {
removed_count = 0;
if (rows.empty()) {
return {};
}
double min_power = rows.front().active_power;
for (const auto& row : rows) {
min_power = std::min(min_power, row.active_power);
}
std::set<size_t> remove_indexes;
for (double interval = min_power; interval < options.rated_power; interval += options.power_step) {
std::unordered_map<std::string, std::vector<size_t>> grouped_by_date;
for (size_t i = 0; i < rows.size(); ++i) {
const auto& row = rows[i];
if (row.active_power >= interval &&
row.active_power < interval + options.power_step) {
grouped_by_date[row.date].push_back(i);
}
}
for (const auto& group : grouped_by_date) {
if (group.second.empty()) {
continue;
}
double min_wind = rows[group.second.front()].wind_speed;
double max_wind = min_wind;
for (size_t index : group.second) {
min_wind = std::min(min_wind, rows[index].wind_speed);
max_wind = std::max(max_wind, rows[index].wind_speed);
}
if (max_wind - min_wind > options.wind_speed_change_threshold) {
remove_indexes.insert(group.second.begin(), group.second.end());
}
}
}
std::vector<ValidRow> result;
result.reserve(rows.size());
for (size_t i = 0; i < rows.size(); ++i) {
if (remove_indexes.count(i) == 0) {
result.push_back(rows[i]);
} else {
removed_points.push_back(RemovedPoint{rows[i], "limit_power"});
}
}
removed_count = static_cast<int>(remove_indexes.size());
return result;
}
template <typename ValueGetter>
std::vector<ValidRow> FilterByWindBinIqr(const std::vector<ValidRow>& rows,
const CalculationOptions& options,
ValueGetter value_getter,
int& removed_count,
std::vector<RemovedPoint>& removed_points,
const std::string& reason) {
removed_count = 0;
if (rows.empty()) {
return {};
}
double min_wind = rows.front().wind_speed;
double max_wind = min_wind;
for (const auto& row : rows) {
min_wind = std::min(min_wind, row.wind_speed);
max_wind = std::max(max_wind, row.wind_speed);
}
std::vector<ValidRow> result;
result.reserve(rows.size());
for (double interval = min_wind; interval < max_wind;
interval += options.cleaning_wind_speed_step) {
std::vector<ValidRow> interval_rows;
std::vector<double> values;
for (const auto& row : rows) {
if (row.wind_speed >= interval &&
row.wind_speed < interval + options.cleaning_wind_speed_step) {
interval_rows.push_back(row);
values.push_back(value_getter(row));
}
}
if (interval_rows.empty()) {
continue;
}
if (interval_rows.size() >= 4) {
const double q1 = Quantile(values, 0.25);
const double q3 = Quantile(values, 0.75);
const double iqr = q3 - q1;
const double lower = q1 - options.iqr_lower_multiplier * iqr;
const double upper = q3 + options.iqr_upper_multiplier * iqr;
for (const auto& row : interval_rows) {
const double value = value_getter(row);
if (value >= lower && value <= upper) {
result.push_back(row);
} else {
++removed_count;
removed_points.push_back(RemovedPoint{row, reason});
}
}
} else {
result.insert(result.end(), interval_rows.begin(), interval_rows.end());
}
}
return result;
}
std::string DatePart(const std::string& time_text) {
if (time_text.size() >= 10) {
return time_text.substr(0, 10);
}
return "";
}
} // namespace
void WindPowerController::StartJob(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback) {
std::string error;
const auto body = ParseBody(req, error);
if (!body.has_value()) {
SendError(callback, kErrorInvalidRequest, error);
return;
}
if (!body->contains("files") || !(*body)["files"].is_array() ||
!body->contains("mapping") || !(*body)["mapping"].is_object()) {
SendError(callback, kErrorInvalidRequest, "缺少 files 或 mapping 参数");
return;
}
try {
fs::create_directories(JobsRoot());
const auto job_id = GenerateJobId();
fs::create_directories(JobDir(job_id));
std::ofstream meta(JobDir(job_id) / "metadata.json", std::ios::trunc);
meta << body->dump(2);
meta.close();
std::ofstream rows(JobRowsPath(job_id), std::ios::trunc);
rows.close();
json data;
data["job_id"] = job_id;
SendSuccess(callback, data);
} catch (const std::exception&) {
SendError(callback, kErrorServer, "创建计算任务失败");
}
}
void WindPowerController::UploadChunk(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback) {
std::string error;
const auto body = ParseBody(req, error);
if (!body.has_value()) {
SendError(callback, kErrorInvalidRequest, error);
return;
}
const auto job_id = GetStringField(*body, "job_id");
if (!job_id.has_value() || !IsSafeJobId(job_id.value()) ||
!fs::exists(JobDir(job_id.value()))) {
SendError(callback, kErrorJobNotFound, "计算任务不存在");
return;
}
if (!body->contains("rows") || !(*body)["rows"].is_array()) {
SendError(callback, kErrorInvalidRequest, "缺少 rows 参数");
return;
}
try {
std::ofstream out(JobRowsPath(job_id.value()), std::ios::app);
int accepted = 0;
for (const auto& row : (*body)["rows"]) {
if (!row.is_object()) {
continue;
}
out << row.dump() << '\n';
++accepted;
}
out.close();
json data;
data["accepted_rows"] = accepted;
SendSuccess(callback, data);
} catch (const std::exception&) {
SendError(callback, kErrorServer, "保存分片数据失败");
}
}
void WindPowerController::FinishJob(
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback) {
std::string error;
const auto body = ParseBody(req, error);
if (!body.has_value()) {
SendError(callback, kErrorInvalidRequest, error);
return;
}
const auto job_id = GetStringField(*body, "job_id");
if (!job_id.has_value() || !IsSafeJobId(job_id.value()) ||
!fs::exists(JobRowsPath(job_id.value()))) {
SendError(callback, kErrorJobNotFound, "计算任务不存在");
return;
}
const CalculationOptions options = ParseOptions(*body);
std::vector<ValidRow> parsed_rows;
std::unordered_map<std::string, int> invalid_reasons;
int raw_rows = 0;
try {
std::ifstream in(JobRowsPath(job_id.value()));
std::string line;
while (std::getline(in, line)) {
if (Trim(line).empty()) {
continue;
}
++raw_rows;
json row;
try {
row = json::parse(line);
} catch (const std::exception&) {
AddInvalid(invalid_reasons, "invalid_json");
continue;
}
const auto time_text = GetStringField(row, "time");
const auto fan_id = GetStringField(row, "fan_id");
const auto wind_speed = GetDoubleField(row, "wind_speed");
const auto active_power = GetDoubleField(row, "active_power");
const auto generator_speed = GetDoubleField(row, "generator_speed");
if (!active_power.has_value() || !std::isfinite(active_power.value()) ||
active_power.value() <= 0.0) {
AddInvalid(invalid_reasons, "invalid_active_power");
continue;
}
if (!generator_speed.has_value() || !std::isfinite(generator_speed.value()) ||
generator_speed.value() <
options.generator_speed_k * options.minimum_generator_speed) {
AddInvalid(invalid_reasons, "invalid_generator_speed");
continue;
}
if (!time_text.has_value()) {
AddInvalid(invalid_reasons, "invalid_time");
continue;
}
const auto timestamp = ParseTime(time_text.value());
if (!timestamp.has_value()) {
AddInvalid(invalid_reasons, "invalid_time");
continue;
}
if (!fan_id.has_value() || fan_id.value().empty()) {
AddInvalid(invalid_reasons, "empty_fan_id");
continue;
}
if (!wind_speed.has_value() || !std::isfinite(wind_speed.value()) ||
wind_speed.value() <= 0.0) {
AddInvalid(invalid_reasons, "invalid_wind_speed");
continue;
}
ValidRow valid_row;
valid_row.time = time_text.value();
valid_row.timestamp = timestamp.value();
valid_row.date = DatePart(time_text.value());
valid_row.fan_id = fan_id.value();
valid_row.wind_speed = wind_speed.value();
valid_row.active_power = active_power.value();
valid_row.generator_speed = generator_speed.value();
parsed_rows.push_back(valid_row);
}
} catch (const std::exception&) {
SendError(callback, kErrorServer, "读取任务数据失败");
return;
}
std::sort(parsed_rows.begin(), parsed_rows.end(), [](const ValidRow& left, const ValidRow& right) {
if (left.fan_id != right.fan_id) {
return left.fan_id < right.fan_id;
}
return left.timestamp < right.timestamp;
});
std::vector<ValidRow> deduped_rows;
std::set<std::string> seen_keys;
int duplicate_rows = 0;
for (const auto& row : parsed_rows) {
const auto key = row.fan_id + "|" + std::to_string(row.timestamp);
if (!seen_keys.insert(key).second) {
++duplicate_rows;
continue;
}
deduped_rows.push_back(row);
}
if (duplicate_rows > 0) {
AddInvalid(invalid_reasons, "duplicate_time");
}
invalid_reasons["duplicate_time"] = duplicate_rows;
std::unordered_map<std::string, std::vector<ValidRow>> rows_by_fan;
for (const auto& row : deduped_rows) {
rows_by_fan[row.fan_id].push_back(row);
}
json fans = json::array();
json curves = json::object();
json bins = json::object();
json scatter_points = json::object();
json filtered_points = json::object();
std::vector<std::string> fan_ids;
fan_ids.reserve(rows_by_fan.size());
for (const auto& item : rows_by_fan) {
fan_ids.push_back(item.first);
}
std::sort(fan_ids.begin(), fan_ids.end());
int limit_power_count = 0;
int tip_speed_ratio_outlier_count = 0;
int speed_power_outlier_count = 0;
int cleaned_rows_count = 0;
for (const auto& fan_id : fan_ids) {
fans.push_back(fan_id);
auto fan_rows = rows_by_fan[fan_id];
std::vector<RemovedPoint> fan_removed_points;
int removed = 0;
fan_rows = FilterLimitPower(fan_rows, options, removed, fan_removed_points);
limit_power_count += removed;
for (auto& row : fan_rows) {
row.tip_speed_ratio = row.generator_speed * 3.14 * 162.0 * 78.0 * 30.0 /
row.wind_speed;
}
fan_rows = FilterByWindBinIqr(
fan_rows,
options,
[](const ValidRow& row) { return row.tip_speed_ratio; },
removed,
fan_removed_points,
"tip_speed_ratio_outlier");
tip_speed_ratio_outlier_count += removed;
fan_rows = FilterByWindBinIqr(
fan_rows,
options,
[](const ValidRow& row) { return row.active_power; },
removed,
fan_removed_points,
"speed_power_outlier");
speed_power_outlier_count += removed;
cleaned_rows_count += static_cast<int>(fan_rows.size());
json fan_scatter = json::array();
for (const auto& row : fan_rows) {
json point;
point["wind_speed"] = row.wind_speed;
point["active_power"] = row.active_power;
fan_scatter.push_back(point);
}
scatter_points[fan_id] = fan_scatter;
json fan_filtered = json::array();
for (const auto& removed_point : fan_removed_points) {
json point;
point["wind_speed"] = removed_point.row.wind_speed;
point["active_power"] = removed_point.row.active_power;
point["reason"] = removed_point.reason;
fan_filtered.push_back(point);
}
filtered_points[fan_id] = fan_filtered;
json fan_curve = json::array();
json fan_bins_json = json::array();
const double curve_min = 1.0 - options.curve_wind_speed_step * 0.5;
const double curve_max = 25.0 + options.curve_wind_speed_step * 0.5;
for (double start = curve_min; start < curve_max; start += options.curve_wind_speed_step) {
const double end = start + options.curve_wind_speed_step;
std::vector<double> values;
for (const auto& row : fan_rows) {
if (row.wind_speed > start && row.wind_speed <= end) {
values.push_back(row.active_power);
}
}
if (!values.empty()) {
json point;
point["wind_speed_start"] = start;
point["wind_speed_end"] = end;
point["wind_speed"] = (start + end) / 2.0;
point["sample_count"] = values.size();
point["average_power"] = Mean(values);
point["median_power"] = Quantile(values, 0.5);
point["stddev_power"] = StdDev(values, point["average_power"].get<double>());
point["p25_power"] = Quantile(values, 0.25);
point["p75_power"] = Quantile(values, 0.75);
point["confidence"] = "脚本分箱";
fan_curve.push_back(point);
fan_bins_json.push_back(point);
}
}
curves[fan_id] = fan_curve;
bins[fan_id] = fan_bins_json;
}
invalid_reasons["limit_power"] = limit_power_count;
invalid_reasons["tip_speed_ratio_outlier"] = tip_speed_ratio_outlier_count;
invalid_reasons["speed_power_outlier"] = speed_power_outlier_count;
const int invalid_total = raw_rows - cleaned_rows_count;
json summary;
summary["raw_rows"] = raw_rows;
summary["valid_rows"] = std::max(0, cleaned_rows_count);
summary["invalid_rows"] = std::max(0, invalid_total);
summary["duplicate_rows"] = duplicate_rows;
summary["limit_power_rows"] = limit_power_count;
summary["tip_speed_ratio_outlier_rows"] = tip_speed_ratio_outlier_count;
summary["speed_power_outlier_rows"] = speed_power_outlier_count;
summary["invalid_reasons"] = CounterJson(invalid_reasons);
summary["fan_count"] = fans.size();
json data;
data["summary"] = summary;
data["fans"] = fans;
data["curves"] = curves;
data["bins"] = bins;
data["scatter_points"] = scatter_points;
data["filtered_points"] = filtered_points;
try {
fs::remove_all(JobDir(job_id.value()));
} catch (const std::exception&) {
// 任务结果已经生成,临时文件清理失败不影响本次响应。
}
SendSuccess(callback, data);
}
void WindPowerController::DeleteJob(
const HttpRequestPtr&,
std::function<void(const HttpResponsePtr&)>&& callback,
const std::string& job_id) {
if (!IsSafeJobId(job_id)) {
SendError(callback, kErrorInvalidRequest, "任务编号非法");
return;
}
try {
fs::remove_all(JobDir(job_id));
SendSuccess(callback);
} catch (const std::exception&) {
SendError(callback, kErrorServer, "清理任务失败");
}
}
@@ -0,0 +1,31 @@
#ifndef WINDPOWERCONTROLLER_H
#define WINDPOWERCONTROLLER_H
#include <drogon/HttpController.h>
#include <nlohmann/json.hpp>
#include "utils/ResponseUtil.h"
using namespace drogon;
class WindPowerController : public drogon::HttpController<WindPowerController, false> {
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(WindPowerController::StartJob, "/api/wind/jobs/start", Post);
ADD_METHOD_TO(WindPowerController::UploadChunk, "/api/wind/jobs/chunk", Post);
ADD_METHOD_TO(WindPowerController::FinishJob, "/api/wind/jobs/finish", Post);
ADD_METHOD_TO(WindPowerController::DeleteJob, "/api/wind/jobs/{1}", Delete);
METHOD_LIST_END
void StartJob(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback);
void UploadChunk(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback);
void FinishJob(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback);
void DeleteJob(const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback,
const std::string& job_id);
};
#endif
+31
View File
@@ -0,0 +1,31 @@
#include <drogon/drogon.h>
#include <trantor/utils/Logger.h>
#include <memory>
#include "controllers/WindPowerController.h"
using namespace drogon;
int main() {
// 抑制 Drogon/trantor 内部日志噪声
trantor::Logger::setLogLevel(trantor::Logger::kFatal);
// 加载 Drogon 配置(监听端口 / CORS / 静态资源根目录)
LOG_INFO << "Loading server configuration...";
app().loadConfigFile("config/server_config.json");
app().registerController(std::make_shared<WindPowerController>());
// SPA 前端路由回退:未匹配路径统一返回 index.html,交由前端路由处理
app().setCustom404Page(
HttpResponse::newFileResponse("./web/index.html", "", CT_TEXT_HTML),
false);
LOG_INFO << "wind_server starting...";
// 阻塞运行,直到收到 SIGINT/SIGTERM
app().run();
LOG_INFO << "wind_server stopped";
return 0;
}
+90
View File
@@ -0,0 +1,90 @@
#ifndef HYPEREDGEX_RESPONSEUTIL_H
#define HYPEREDGEX_RESPONSEUTIL_H
#include <nlohmann/json.hpp>
#include <string>
class ResponseUtil {
public:
// 生成成功响应,包含数据
static nlohmann::json GenerateResponse(int status, const std::string& msg, const nlohmann::json& data) {
nlohmann::json response;
response["status"] = status;
response["msg"] = msg;
response["data"] = data;
return response;
}
// 生成成功响应,无数据
static nlohmann::json GenerateResponse(int status, const std::string& msg) {
nlohmann::json response;
response["status"] = status;
response["msg"] = msg;
response["data"] = nullptr;
return response;
}
// 生成成功响应的便捷方法
static nlohmann::json GenerateSuccessResponse(const nlohmann::json& data) {
return GenerateResponse(0, "success", data);
}
// 生成成功响应的便捷方法(无数据)
static nlohmann::json GenerateSuccessResponse() {
return GenerateResponse(0, "success");
}
/**
* 生成成功响应(原始 JSON 字符串版本)
* 用于读配置文件原样返回前端的场景,
* 绕过 nlohmann::json 的 parse/dump 避免丢失 key 顺序。
*/
static std::string GenerateSuccessResponseRaw(const std::string& rawJsonData) {
return "{\"status\":0,\"msg\":\"success\",\"data\":" + rawJsonData + "}";
}
// 生成错误响应的便捷方法
static nlohmann::json GenerateErrorResponse(int status, const std::string& msg) {
return GenerateResponse(status, msg, nullptr);
}
ResponseUtil() = delete; // 禁止实例化
};
// Drogon HTTP 响应便捷辅助
#if __has_include(<drogon/HttpResponse.h>)
#include <drogon/HttpResponse.h>
#include <functional>
using DrogonCallback = std::function<void(const drogon::HttpResponsePtr&)>;
inline void SendJson(DrogonCallback& cb, const nlohmann::json& body,
drogon::HttpStatusCode code = drogon::k200OK) {
auto resp = drogon::HttpResponse::newHttpResponse();
resp->setStatusCode(code);
resp->setBody(body.dump());
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
cb(resp);
}
inline void SendSuccess(DrogonCallback& cb, const nlohmann::json& data) {
SendJson(cb, ResponseUtil::GenerateSuccessResponse(data));
}
inline void SendSuccess(DrogonCallback& cb) {
SendJson(cb, ResponseUtil::GenerateSuccessResponse());
}
inline void SendError(DrogonCallback& cb, int code, const std::string& msg,
drogon::HttpStatusCode http = drogon::k200OK) {
SendJson(cb, ResponseUtil::GenerateErrorResponse(code, msg), http);
}
inline void SendForbidden(DrogonCallback& cb, const std::string& msg = "无权限执行此操作") {
SendError(cb, 3, msg, drogon::k403Forbidden);
}
#endif // __has_include drogon
#endif //HYPEREDGEX_RESPONSEUTIL_H