feat: 연감을 가져오는 API
- Global/GetHistory - 과거 기록. 캐싱 가능 - GetCurrentHistory - 현재 기록. 캐싱 불가
This commit is contained in:
@@ -60,6 +60,10 @@ function chiefTurnTable()
|
|||||||
";
|
";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function templateLimitMsg(string $turntime): string{
|
||||||
|
return "이미 너무 많은 접속을 하셨습니다. 다음 턴에 다시 시도해주세요. (턴시간: {$turntime})";
|
||||||
|
}
|
||||||
|
|
||||||
function displayiActionObjInfo(?iAction $action)
|
function displayiActionObjInfo(?iAction $action)
|
||||||
{
|
{
|
||||||
if ($action === null) {
|
if ($action === null) {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace sammo\API\Global;
|
||||||
|
|
||||||
|
use sammo\Session;
|
||||||
|
use DateTimeInterface;
|
||||||
|
|
||||||
|
use function sammo\getCurrentHistory;
|
||||||
|
|
||||||
|
class GetCurrentHistory extends \sammo\BaseAPI
|
||||||
|
{
|
||||||
|
public function validateArgs(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredSessionMode(): int
|
||||||
|
{
|
||||||
|
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
|
||||||
|
{
|
||||||
|
$history = getCurrentHistory();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'result' => true,
|
||||||
|
'data' => $history,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace sammo\API\Global;
|
||||||
|
|
||||||
|
use sammo\Session;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use sammo\APICacheResult;
|
||||||
|
use sammo\DB;
|
||||||
|
use sammo\Json;
|
||||||
|
use sammo\KVStorage;
|
||||||
|
use sammo\UniqueConst;
|
||||||
|
use sammo\Util;
|
||||||
|
use sammo\Validator;
|
||||||
|
|
||||||
|
use function sammo\checkLimit;
|
||||||
|
use function sammo\getAllNationStaticInfo;
|
||||||
|
use function sammo\getNationStaticInfo;
|
||||||
|
use function sammo\increaseRefresh;
|
||||||
|
use function sammo\templateLimitMsg;
|
||||||
|
|
||||||
|
class GetHistory extends \sammo\BaseAPI
|
||||||
|
{
|
||||||
|
|
||||||
|
public string|null $cacheHash = null;
|
||||||
|
|
||||||
|
public function validateArgs(): ?string
|
||||||
|
{
|
||||||
|
$v = new Validator($this->args);
|
||||||
|
$v->rule('required', ['year', 'month'])
|
||||||
|
->rule('lengthMin', 'serverID', 1)
|
||||||
|
->rule('integer', 'year')
|
||||||
|
->rule('integer', 'month');
|
||||||
|
if (!$v->validate()) {
|
||||||
|
return $v->errorStr();
|
||||||
|
}
|
||||||
|
if (!$this->args['serverID']) {
|
||||||
|
$this->args['serverID'] = UniqueConst::$serverID;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredSessionMode(): int
|
||||||
|
{
|
||||||
|
return static::REQ_LOGIN | static::REQ_READ_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tryCache(): ?APICacheResult
|
||||||
|
{
|
||||||
|
if (!$this->cacheHash) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new APICacheResult(
|
||||||
|
null,
|
||||||
|
$this->getCacheKey($this->args['serverID'], $this->args['year'], $this->args['month'], $this->cacheHash)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getHistory(string $serverID, int $year, int $month): array
|
||||||
|
{
|
||||||
|
$db = DB::db();
|
||||||
|
$history = $db->queryFirstRow(
|
||||||
|
'SELECT * FROM ng_history WHERE server_id = %s AND year = %i AND month = %i',
|
||||||
|
$serverID,
|
||||||
|
$year,
|
||||||
|
$month
|
||||||
|
);
|
||||||
|
$hash = hash(
|
||||||
|
'sha256',
|
||||||
|
"[" . join(',', [
|
||||||
|
$history['global_history'],
|
||||||
|
$history['global_action'],
|
||||||
|
$history['nations'],
|
||||||
|
$history['map'],
|
||||||
|
]) . "]"
|
||||||
|
);
|
||||||
|
$history['global_history'] = Json::decode($history['global_history']);
|
||||||
|
$history['global_action'] = Json::decode($history['global_action']);
|
||||||
|
$history['nations'] = Json::decode($history['nations']);
|
||||||
|
$history['map'] = Json::decode($history['map']);
|
||||||
|
$history['hash'] = $hash;
|
||||||
|
return $history;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseEtag(?string $etag): ?array
|
||||||
|
{
|
||||||
|
if (!$etag) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$tags = explode('-', $etag);
|
||||||
|
if (count($tags) != 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!is_numeric($tags[1])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!is_numeric($tags[2])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [$tags[0], Util::toInt($tags[1]), Util::toInt($tags[2]), $tags[3]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCacheKey(string $serverID, int $year, int $month, string $cacheHash): string
|
||||||
|
{
|
||||||
|
return "{$serverID}-{$year}-{$month}-{$cacheHash}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkCached(?string $reqEtag): ?string
|
||||||
|
{
|
||||||
|
if (!$reqEtag) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$parsedTags = $this->parseEtag($reqEtag);
|
||||||
|
|
||||||
|
if (!$parsedTags) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverID = $this->args['serverID'];
|
||||||
|
$year = Util::toInt($this->args['year']);
|
||||||
|
$month = Util::toInt($this->args['month']);
|
||||||
|
|
||||||
|
[$cachedServerID, $cachedYear, $cachedMonth, $cachedHash] = $parsedTags;
|
||||||
|
if ($cachedServerID !== $serverID || $cachedYear !== $year || $cachedMonth !== $month) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag로 hash를 굳이 붙이고 왔다면, 이미 데이터를 가지고 있다고 가정한다.
|
||||||
|
return $cachedHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
|
||||||
|
{
|
||||||
|
$checkCache = $this->checkCached($reqEtag);
|
||||||
|
if ($checkCache) {
|
||||||
|
$this->cacheHash = $checkCache;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverID = $this->args['serverID'];
|
||||||
|
$year = $this->args['year'];
|
||||||
|
$month = $this->args['month'];
|
||||||
|
|
||||||
|
$db = DB::db();
|
||||||
|
|
||||||
|
if ($serverID === UniqueConst::$serverID) {
|
||||||
|
if (!$session->isGameLoggedIn()) {
|
||||||
|
return '진행중인 서버의 연감은 게임에 로그인해야 볼 수 있습니다.';
|
||||||
|
}
|
||||||
|
increaseRefresh("연감", 1);
|
||||||
|
|
||||||
|
$me = $db->queryFirstRow('SELECT con, turntime FROM general WHERE owner = %i', $session->userID);
|
||||||
|
if (!$me) {
|
||||||
|
return '장수가 사망했습니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$con = checkLimit($me['con']);
|
||||||
|
if ($con >= 2) {
|
||||||
|
return templateLimitMsg($me['turntime']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$f_year, $f_month] = $db->queryFirstList(
|
||||||
|
'SELECT year, month FROM ng_history WHERE server_id = %s ORDER BY year ASC, month ASC LIMIT 1',
|
||||||
|
$serverID
|
||||||
|
);
|
||||||
|
if ($f_year === null || $f_month === null) {
|
||||||
|
return '올바르지 않은 서버 아이디입니다.';
|
||||||
|
}
|
||||||
|
$firstYearMonth = Util::joinYearMonth($f_year, $f_month);
|
||||||
|
|
||||||
|
|
||||||
|
[$l_year, $l_month] = $db->queryFirstList(
|
||||||
|
'SELECT year, month FROM ng_history WHERE server_id = %s ORDER BY year DESC, month DESC LIMIT 1',
|
||||||
|
$serverID
|
||||||
|
);
|
||||||
|
$lastYearMonth = Util::joinYearMonth($l_year, $l_month);
|
||||||
|
|
||||||
|
$queryYearMonth = Util::joinYearMonth($year, $month);
|
||||||
|
if ($queryYearMonth < $firstYearMonth || $queryYearMonth > $lastYearMonth) {
|
||||||
|
return '올바르지 않은 범위입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$history = $this->getHistory($serverID, $year, $month);
|
||||||
|
$this->cacheHash = $history['hash'];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'result' => true,
|
||||||
|
'data' => $history,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-2
@@ -4,7 +4,7 @@ import {
|
|||||||
type APITail, type APICallT, type RawArgType, type ValidResponse, type InvalidResponse
|
type APITail, type APICallT, type RawArgType, type ValidResponse, type InvalidResponse
|
||||||
} from "./util/callSammoAPI";
|
} from "./util/callSammoAPI";
|
||||||
export type { ValidResponse, InvalidResponse };
|
export type { ValidResponse, InvalidResponse };
|
||||||
import { APIPathGen, NumVar } from "./util/APIPathGen.js";
|
import { APIPathGen, NumVar, StrVar } from "./util/APIPathGen.js";
|
||||||
import type { BettingDetailResponse, BettingListResponse } from "./defs/API/Betting";
|
import type { BettingDetailResponse, BettingListResponse } from "./defs/API/Betting";
|
||||||
import type { ReserveBulkCommandResponse, ReserveCommandResponse, ReservedCommandResponse } from "./defs/API/Command";
|
import type { ReserveBulkCommandResponse, ReserveCommandResponse, ReservedCommandResponse } from "./defs/API/Command";
|
||||||
import type { ChiefResponse } from "./defs/API/NationCommand";
|
import type { ChiefResponse } from "./defs/API/NationCommand";
|
||||||
@@ -12,7 +12,7 @@ import type { inheritBuffType } from "./defs/API/InheritAction";
|
|||||||
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse } from "./defs/API/Nation";
|
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse } from "./defs/API/Nation";
|
||||||
import type { UploadImageResponse } from "./defs/API/Misc";
|
import type { UploadImageResponse } from "./defs/API/Misc";
|
||||||
import type { JoinArgs } from "./defs/API/General";
|
import type { JoinArgs } from "./defs/API/General";
|
||||||
import type { GetConstResponse } from "./defs/API/Global";
|
import type { GetConstResponse, GetCurrentHistoryResponse, GetHistoryResponse } from "./defs/API/Global";
|
||||||
import type { GeneralListResponse } from "./defs";
|
import type { GeneralListResponse } from "./defs";
|
||||||
|
|
||||||
const apiRealPath = {
|
const apiRealPath = {
|
||||||
@@ -54,6 +54,11 @@ const apiRealPath = {
|
|||||||
with_token?: boolean
|
with_token?: boolean
|
||||||
}, GeneralListResponse>,
|
}, GeneralListResponse>,
|
||||||
GetConst: GET as APICallT<undefined, GetConstResponse>,
|
GetConst: GET as APICallT<undefined, GetConstResponse>,
|
||||||
|
GetHistory: StrVar('serverID')(
|
||||||
|
NumVar('year',
|
||||||
|
NumVar('month', GET as APICallT<undefined, GetHistoryResponse>
|
||||||
|
))),
|
||||||
|
GetCurrentHistory: GET as APICallT<undefined, GetCurrentHistoryResponse>
|
||||||
},
|
},
|
||||||
InheritAction: {
|
InheritAction: {
|
||||||
BuyHiddenBuff: PUT as APICallT<{
|
BuyHiddenBuff: PUT as APICallT<{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { CityID, CrewTypeID, GameCityDefault, GameConstType, GameUnitType, GameIActionKey, GameIActionCategory, GameIActionInfo } from "@/defs/GameObj";
|
import type { CityID, CrewTypeID, GameCityDefault, GameConstType, GameUnitType, GameIActionKey, GameIActionCategory, GameIActionInfo } from "@/defs/GameObj";
|
||||||
|
import type { MapResult } from "@/defs";
|
||||||
|
|
||||||
export interface GetConstResponse {
|
export interface GetConstResponse {
|
||||||
result: true;
|
result: true;
|
||||||
@@ -33,4 +34,37 @@ export interface GetConstResponse {
|
|||||||
allItems: "item";
|
allItems: "item";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HistoryObj = {
|
||||||
|
server_id: string,
|
||||||
|
year: number;
|
||||||
|
month: number;
|
||||||
|
map: MapResult,
|
||||||
|
global_history: string[],
|
||||||
|
global_action: string[],
|
||||||
|
nations: {
|
||||||
|
capital: number,
|
||||||
|
cities: string[],
|
||||||
|
color: string,
|
||||||
|
gennum: number,
|
||||||
|
level: number,
|
||||||
|
name: string,
|
||||||
|
nation: number,
|
||||||
|
power: number,
|
||||||
|
type: GameIActionKey
|
||||||
|
}[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetHistoryResponse = {
|
||||||
|
result: true;
|
||||||
|
data: HistoryObj & {
|
||||||
|
hash: string;
|
||||||
|
//no: number,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetCurrentHistoryResponse = {
|
||||||
|
result: true;
|
||||||
|
data: HistoryObj;
|
||||||
}
|
}
|
||||||
@@ -228,4 +228,23 @@ export const diplomacyStateInfo: Record<diplomacyState, diplomacyInfo> = {
|
|||||||
1: { name: '선포중', color: 'magenta' },
|
1: { name: '선포중', color: 'magenta' },
|
||||||
2: { name: '통상' },
|
2: { name: '통상' },
|
||||||
7: { name: '불가침', color: 'green' },
|
7: { name: '불가침', color: 'green' },
|
||||||
|
}
|
||||||
|
|
||||||
|
//Map
|
||||||
|
type MapCityCompact = [number, number, number, number, number, number];
|
||||||
|
type MapNationCompact = [number, string, string, number];
|
||||||
|
export type MapResult = {
|
||||||
|
result: true,
|
||||||
|
startYear: number,
|
||||||
|
year: number,
|
||||||
|
month: number,
|
||||||
|
cityList: MapCityCompact[],
|
||||||
|
nationList: MapNationCompact[],
|
||||||
|
spyList: Record<number, number>,
|
||||||
|
shownByGeneralList: number[],
|
||||||
|
myCity?: number,
|
||||||
|
myNation?: number,
|
||||||
|
|
||||||
|
theme?: string,
|
||||||
|
history?: string[],
|
||||||
}
|
}
|
||||||
+3
-21
@@ -2,7 +2,7 @@ import axios from 'axios';
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { isNumber, merge } from 'lodash';
|
import { isNumber, merge } from 'lodash';
|
||||||
import { convColorValue, convertDictById, stringFormat } from '@/common_legacy';
|
import { convColorValue, convertDictById, stringFormat } from '@/common_legacy';
|
||||||
import type { InvalidResponse } from '@/defs';
|
import type { InvalidResponse, MapResult } from '@/defs';
|
||||||
import { unwrap } from "@util/unwrap";
|
import { unwrap } from "@util/unwrap";
|
||||||
import { convertFormData } from '@util/convertFormData';
|
import { convertFormData } from '@util/convertFormData';
|
||||||
import { exportWindow } from '@util/exportWindow';
|
import { exportWindow } from '@util/exportWindow';
|
||||||
@@ -28,9 +28,6 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapCityCompact = [number, number, number, number, number, number];
|
|
||||||
type MapNationCompact = [number, string, string, number];
|
|
||||||
|
|
||||||
type MapCityParsedRaw = {
|
type MapCityParsedRaw = {
|
||||||
id: number,
|
id: number,
|
||||||
level: number,
|
level: number,
|
||||||
@@ -77,21 +74,6 @@ type MapNationParsed = {
|
|||||||
capital: number
|
capital: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapResult = {
|
|
||||||
result: true,
|
|
||||||
startYear: number,
|
|
||||||
year: number,
|
|
||||||
month: number,
|
|
||||||
cityList: MapCityCompact[],
|
|
||||||
nationList: MapNationCompact[],
|
|
||||||
spyList: Record<number, number>,
|
|
||||||
shownByGeneralList: number[],
|
|
||||||
myCity?: number,
|
|
||||||
myNation?: number,
|
|
||||||
|
|
||||||
theme?: string,
|
|
||||||
history?: string[],
|
|
||||||
}
|
|
||||||
type MapRawResult = InvalidResponse | MapResult;
|
type MapRawResult = InvalidResponse | MapResult;
|
||||||
|
|
||||||
function is_touch_device(): boolean {
|
function is_touch_device(): boolean {
|
||||||
@@ -285,7 +267,7 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world
|
|||||||
async function convertCityObjs(obj: MapResult): Promise<MapCityDrawable> {
|
async function convertCityObjs(obj: MapResult): Promise<MapCityDrawable> {
|
||||||
//원본 Obj는 굉장히 간소하게 온다, Object 형태로 변환해서 사용한다.
|
//원본 Obj는 굉장히 간소하게 온다, Object 형태로 변환해서 사용한다.
|
||||||
|
|
||||||
function toCityObj([id, level, state, nationID, region, supply]: MapCityCompact): MapCityParsedRaw {
|
function toCityObj([id, level, state, nationID, region, supply]: MapResult['cityList'][0]): MapCityParsedRaw {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
level: level,
|
level: level,
|
||||||
@@ -296,7 +278,7 @@ export async function reloadWorldMap(option: loadMapOption, drawTarget = '.world
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toNationObj([id, name, color, capital]: MapNationCompact): MapNationParsed {
|
function toNationObj([id, name, color, capital]: MapResult['nationList'][0]): MapNationParsed {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
|||||||
Reference in New Issue
Block a user