AnQiCMS v3.8.1 Update Log
⚠️ Breaking Change
archives/archive_draftsDeleted 29 historical columns. Two types of columns: ① 23 columns for mall use (brandvideo_urlneed_logisticsis_free_shippingsold_countreview_countaverage_scoreoption_typeweightweight_unitlengthwidthheightvolumelength_unitallow_oversoldorder_fieldsmin_quantitymax_quantitystep_quantityproduct_typeorigin_pricecost_price); ② 6 dead columns with zero warehouse references (deleted_timelogosku_typerating_countis_wholesalehas_order_fields)pricewithstockRetained in the main table——stockContains a complete set of atomic expressions (order deduction, return order replenishment, SKU summary recalculation), which cannot be completed with a single UPDATE statement when splitting the table;priceAt the same time, it bears two meanings: the price of the commodity and the threshold for paid reading.- The product attributes were moved to
archive_productsSide table:archive_idPrimary key, shares the same set of IDs with the formal draft/rough draft. Only move between the main table when published or withdrawn, the side table does not move along. Only build rows for products that have been matched with properties, do not leave empty records in articles. Reading is handled by GORM'sQuery().Aftercallback once for IN query batch completion, write byArchive.AfterSavedrop the side table,ArchiveAll field names on the structure and tag are retained, the front-end and template contract does not change. - The upgrade sequence has a strict constraint: Fill in first, then delete columns; if the fill-in fails, do not delete columns (no marking, retry next startup). No need to do version灰度 during the same startup.
- The SQL without dependencies on old columns will fail:
WHERE product_type = ?This condition has been changed toLEFT JOIN archive_products+IFNULL(...,1)(Keep the case where the ordinary goods have no side table line). If you have developed a plugin to directly query these columns, you need to adjust them accordingly. - The attachment upload has changed to a whitelist of file extensions, defaulting to rejectThe whitelist includes images, videos, audio, documents, compressed files, and fonts (
.ttfIs a hard dependency for the title picture),.psd.ai.apkThe suffixes that could be uploaded previously will now be rejected. The judgment is unified to gonormalizeExt(Remove spaces, convert to lowercase, and add a leading point),.PHP5/php5/"php5 "Refuse according to the same rules. The affected entries are attachment upload, template design import, watermark and title image plugin; if additional suffixes are needed, please fill in the table as required. See 'Security Repair' for details. - The 'Can Post Articles' category of the user group is empty after the upgrade, meaning that it cannot post articles after the upgrade.Authorization is empty, all fail closed, there is no 'empty means all' as a fallback, and parent-child categories do not inherit. 'Select All' is expressed by checking all nodes on the front end. Users who could post before the upgrade will immediately receive a permission error and need to check the 'User Group → Can Post Articles' again before they can continue to post.
- The mall function is turned off by default.:
system.shop_modeDefault0(Normal content mode), 38 routes of the mall front-end are returned 404 in this mode, and the back-end can still manage mall data (only the menu is hidden). To enable the mall on the content site, you need to switch the mode in the system settings and save.
✨ New Feature
- The mall capabilities are integrated into the trunk as a whole: Added 24 data models (SKU, shopping cart, wish list, coupons, marketing activities, wholesale rules, order services, logistics freight, inquiry communication orders, product synchronization, points, exchange rates, etc., 11 model files / 13 provider files / 12 controller files).The order settlement includes coupons, shipping fees, member prices, promotional prices, distribution, invoices and logistics tracking; compatible with multiple currencies, Google Places, Google Merchant product synchronization, Brevo email service provider, 15 sets of mall email templates (order / shipping / completion / refund / invoice / order recall / communication order, etc.) and a set of mall templates. Template tags added
archiveSku/reviewThe front-end interface fully supports mall semantics with GraphQL. The AI system, Partial semantics, SQL injection reinforcement, and payment account system in the trunk version 3.6.7 are all retained. - Switch between normal/mall mode: Added
shop_modeConfigure (0 General content site / 1 Mall). The mall front-end routing is controlled in normal modeShopModeGuardAll routes are unified to 404, and the management background does not intercept; template tags do not render mall elements in normal mode. The guard list is double-locked at the source code level, and any leaks or accidental deletions will turn red - Mall endpoint access to MCP: Added four trading domain intentions -
commerce_marketing(Coupons + Marketing activities, 15 endpoints),commerce_fulfillment(Freight rules + Inquiry / In-site messages, 12 endpoints),commerce_review(Evaluation + Value-added services, 8 endpoints),seo_merchant(Google Merchant product synchronization, 9 endpoints), a total of 44 actions. All four intents are turned off by default (transactions involve real money, the site needs to be explicitly opened), and two parallel lists are registered simultaneously. - Message channels: Feishu / DingTalk access: Enterprise IM uses a long connection (Feishu oapi-sdk / DingTalk Stream mode), no public callback address is needed, and no open incoming port is required. It does two things: ① When the website generates messages, orders, and other events, they are pushed to the specified group or the private chat of the bound administrator according to the notification rules; ② The administrator directly communicates with the site management in the private chat or authorized groups. It includes channel connection management, one-time token identity binding, session target and notification rules, operation records, and templated notification text.
- Channel interaction details: Confirm with an emoji when receiving a message (Feishu OnIt emoji, DingTalk emoji reply), reducing the silence when waiting for models and tools, and only acknowledging messages that will be executed; model replies are rendered in Markdown on Feishu/DingTalk (compatible with CRLF for分段, DingTalk skips code fences and strips marks according to CommonMark); the organization identifier is changed to be automatically obtained from the platform interface and supports anchoring the first event, no longer requiring webmasters to manually fill in values that are not visible in the platform backend, and manual configuration is always prioritized.
- Added two payment channels: Tiger pepper (MD5 signature implemented according to the official V3 document, callback verification signature + appid attribution +
status=OD+total_feeYuan to cents, rounding down, refunds only support whole orders, gateway address can be configured so that the backup domain name can be switched when the main station fails), NOWPayments (cryptocurrency, invoice mode,order_idfixed transmissionpayment.PaymentId,IPN uses HMAC-SHA512 verification, recognizing only finished/confirmed/sending, and explicitly reporting an error instead of pretending the refund was successful when there is no refund API). Both channels cover order placement, asynchronous callback, proactive order inquiry, and refunding for four links. - User groups can authorize column submission: Added relationship table
user_group_categories(fall into a many-to-many relationship table instead of being stuffed in)user_groups.settingThe JSON, otherwise, when deleting the column, it is not possible to clear the authorization, nor can it be cross-checked according to the column), the user group editing pop-up adds column multiple choice (the full/semi-selected state is derived from value, and state is not saved separately). Front-end submission checks authorization by column, and refuses the entire order unilaterally and names the column if not authorized; when updating an existing document and no column is transmitted, check according to the existing columns in the library, blocking the 'first submitted to the authorized column, and then change the content on the spot' to bypass the later revoked authorization. - Editor and content entry: The link dialog supports filling in the relative path within the site(
input[type=url]The browser's native validation only recognizes absolute addresses,/about.html/../tag/seoCannot submit directly, change to text aftersetLinkThe protocol will still be verified, it will not be addedjavascript:);The associated document is changed to use a unified selector (controlled component, supports module filtering, cross-page multiple selection retention, selected by id in batch), six call points are unified, and the selected ones will no longer be lost when changing pages - Full-text search enhancementThe status interface returns the actual number of indexed documents within the engine (previously only 'Rebuild progress current/total', interruptions, write rejections, and index cleared externally were all the same), each of the four engines Elasticsearch / Meilisearch / Zinc / Wukong implements counting; keyword-related documents take precedence over full-text indexing (first get the ID by index, then use columns, modules, and exclusion conditions to return the table, reorder by index ranking, continue flipping through index pages when filtered too much), LIKE demotion path extra deduplication, change to prefix matching when the number of documents exceeds 100,000.
⚡ Feature Optimization
- Zero value saves the pointer semantics: Save class endpoint universally
if req.X > 0/!= ""Judging whether to write this field causes two types of silent failures - one is that zero value cannot be written in (inventory changed to 0 indicates sold out, price changed to 0 indicates free, member status changed to pending review or disabled, SEO title cleared, interface returns ok but the database does not change); the other is local update reverse clearing (when only one field is passed to AI/MCP, the rest is covered by zero value, tested only to change member username, status from 1 to 0, phone number and invitation code are cleared together).Archive/Category/ModuleRequest/NavConfig/PluginTag/UserRequest/UserGroupRequestAll zero-value sensitive fields are changed to pointers (nil = not provided, non-nil = explicitly written with 0/false/empty string), new.library.AssignUniform 'full coverage / partial update', clear album, tags, and association all use 'pass an empty array' expression and delete four implicit inference fields. Also fix a batch of unreachable states caused by hard-coded issues: tags and navigation can never be hidden, changing the user group name will clear the level and price and force the disabled group to be enabled, and changing the model keyword will never take effect. - Index completion and column length contraction: Add indexes for bidirectional association with tags and title queries;
utf8mb4Order column index prefix limit 767 bytes,varchar(250)Creating index will fail directly (Specified key was too long), The string columns with index are uniformly shortened to the existing convention in the warehouse 190 characters,coupon_code.codeReceived 128 (enough for the redemption code). - List performance: Tag list
listPattern skipcount(*)- 160,000 linestagsOn the table, 974 slow logs where 950 are the same full table scan (0.6~2.0s, CPU full under concurrency), while 3 of the 4 call sides have not read total from start to finish; count is changed to 300 seconds process cache plus estimation, line count ≥ 100,000 no longer truly scan the entire table. Adjust the callback order for batch loading of products to eliminate sequential supplement checks, empty AI configuration into cache and synchronize failure to avoid repeated database queries and concurrent backfilling of old values. - City station routing and template bottom cover reception:
placeof/{filename}The rule no longer acts as a general fallback (previously, any unmatched URL was judged to be a city station detail page, category page, and list page with the entire site 404), subdomain resolution no longer uses an empty token to search the database; when a city station page template is missing, it is handled as a 404, and the error page is no longer rendered and written into the cache (the on-site problem of accessing a city station page after switching to the mall template). Both have added guardrails. - Backend writes endpoint uniform parameter guardrail: Extracted
payload_guard.go, for empty load (""/{}/null), lack of target, target does not exist, uniformly reject and return clear text, replacing more than 50 repeated ones.ifJudgment; deletion and update hit 0 rows no longer silent successful (RowsAffectedReported for 0ErrRecordNotFound, but update needs to be checked once, otherwise 'set to the same value repeatedly' will be mistakenly judged as non-existent). - Email link: The administrator's notification has changed
SendMailAutomatically determine the format (previously the rendered HTML was hard-coded into plain text and sent, the inbox was full of)<p>/<br>); attachments will no longer be discarded, invoices and scheduled emails can be delivered normally; template injectionpriceFormat/stampToDate, syntax error returns error instead of panic; multiple recipient errors are usederrors.JoinAggregate, the failures of the previous recipients are no longer covered by the later ones; the recall of abandoned order emails is changed to the existing ones./order/checkoutPayment link. - The front-end is integrated into the main warehouse.:
anqicms-admin/Tracking with the main warehouse (508 source files, excluding dependencies and build products), the front and back ends are still only coupled through HTTP contracts. New.make adminTarget,yarn buildAutomatically putdistSync tosystem/——Previously, it could only rely on manual copying by hand, which is a fatal silent error for OEM (the interface still works normally, only when the customer takes a screenshot will it be found that the brand has not changed). The build tools are explicitly run on the host platform during cross-compilation:make build GOOS=...The command-line variables will be automatically exported to the recipe environment,go run ./cmd/oemgenCompile an executable file for the target platform and report an error when executing on this machineexec format error. - Upgrade dependencies: Go 1.26.0 → 1.26.10; eino 0.9.23, MCP go-sdk 1.8.0, goquery 1.13, gopay 1.5.123, meilisearch-go 0.36.3, gomarkdown and others synchronized upgrade; with the introduction of Feishu oapi-sdk, DingTalk stream-sdk, Brevo SDK and google/uuid with mall and channel features; mysql driver changed to direct dependency; cleaned up the warehouse without references
go-nowpaymentswitheris(NOWPayments is changed to implement IPN verification by yourself, no longer dependent on third-party SDKs). - Miscellaneous: Regenerate the API directory (482 backend endpoints,
apicatalog -checkconsistent with the source code); complete 25 mall write endpoint summaries and 59 URLParam parameter descriptions (the endpoint table at that time had 458 descriptions all around); menu removedgoogle/llmsold top-level entry (two pages have been respectively included in/plugin/channel/googlewith/plugin/seo/llms); move the backend help and template tag links todocs.anqicms.com; newAGENTS.mdcollaboration standards and.graphifyignore. archivesclean up 4 redundant indexes:idx_articles_url_token(andidx_archives_url_tokenIn the same column,idx_fixed_link(andidx_archives_fixed_linkIn the same column,idx_category_id(isidx_category_archive_idThe leftmost prefix of theidx_updated_time(The column with the wrong index iscreated_timeandidx_created_timeCompletely repeated). The last one is not just deleted: after deleting,updated_timeRebuild the same index on the column, because the draft is cleaned up at regular intervals andtimefactorAll according toupdated_timeFiltered. The basis for judgment is the actual index column rather than the index name——archive_draftsofidx_updated_timeThe index isupdated_time, cannot be deleted together.}apicatalogThe generator recognizes parameter extraction packaging function:readJSONPayload(ctx, &req)This package-level function was not previously recognized (only recognizedctx.Xxx()in this form), regenerating the endpoint list will silently clear the parameter schema of 53 endpoints, and MCP'sapi_invokewill read this data with the parameter gate at the intent layer.
🔒 Security fix
- The asynchronous callbacks of the four payment channels were all silently disabled previously: WeChat thinks
VerifySignThe first parameter is appId (actually the merchant apiKey), and the signature algorithm is hardcoded as MD5, and the notification message does not includesign_type, and the order uses HMAC-SHA256 - all WeChat payment notifications fail the signature check; Stripe usesevt_Check event IDpayment_idIf not found, return directly, Iris defaults to 200 empty response to let Stripe think the delivery was successful and not retry; PayPal requires the handling function?token=That is synchronousreturn_urlof the parameters), but the registered webhook address does not carry parameters, and all callbacks are blocked at the gate; Alipay's signature verification itself is normal, but does not verify at alltrade_status,WAIT_BUYER_PAYwill also mark the order as paid. All four are fixed according to the root cause (Stripe changes according toterrace_idlocation,metadata.payment_idbackup; PayPal is changed toVerifyWebhookSignatureauthentication, signature verification failure returns 400; Alipay supplementstrade_status/app_id/total_amountVerification, and fix the parsing error ignored (disguised as payment-not-found),result_codeOrder changed even if not SUCCESS,total_feeNot consistent with order amount,pay_wayChanged to wechat and other issues uniformly. - Payment accounting plus concurrent gate,: Extracted
Website.MarkPaymentPaid(Paid settlement + financial flow + order promotion), usingpaid_time = 0Condition update gate - notify retry and template polling/api/payment/checkWill come in concurrently, without a gate, it will repeat financial flow records. - Password recovery can be email hijacked: Reason for credential reset
Md5(email + password)Derived, empty password accounts (Google / Public account / Verification code registration) will degrade toMd5(email)Know the email and you can change the password to log in. Change to using the siteTokenSecretCommon derivation, and the sending end and the verification end must call the same function (otherwise the normal user's reset will fail); at the same time fixstate=resetThe problem that clearing the verification code in advance will cause the submission to fail after jumping. - Email enumeration can bypass the flow control: Registration,
GET/POST verify/emailThree endpoints can detect whether a certain email is registered, originally each with unlimited flow, the attacker can bypass by switching the probe. The three probes are unified hungAccountProbeGuard(20 times / 10 minutes / IP, limit exceeded 429, 9 language packs synchronized with additional text);state=existNo longer reply{id, email}(ID can be spliced)/api/comment/listReverse lookup anonymous commenters), only reply{"exist": true},codeMaintain semantics unchanged to avoid template branch failure. - Attachment upload can land script trojan: Online
public/uploadsOccurredshell.php5/s2.phtmlThe root cause is only intercepting.phpwith.jspWhile content not recognized by filetype will fall back to the user filename, the rest of the executable extensions will be written to disk as is. Change to the default rejected extension whitelist; Watermark and title image upload will no longer take the user filename suffix, instead it will beimage.DecodeThe result determines (blocking the 'legitimate GIF tail appending PHP code, save as .phtml' this landing path); the template package importstatic/The directory goes through an independent whitelist, and fixes the Zip Slip (within the zip)static/../../x.phtmlCan no longer write topublic/Outside). /aigenerateLacks authorization middleware:manageThe subgroup itself only hungParseAdminUrlThe entire group of 7 endpoints can read AI configuration, trigger batch generation (实测回 'AI generation task has been triggered' but the schedule table, log table has no change). SupplementParseAdminToken+AdminPermissionAfter the change, it is unified without tokencode=1001.- MCP export leaks clear text certificate: The site library password and various tokens could be returned with the tool results before. The exit is unified to desensitize, and the site list is changed to a secure view; the same path is used to check back and restore the true value, and if it cannot be restored, it is refused to be written (otherwise, the sentinel value will be truly written into the database). At the same time, it is given
page_sizeIncrease the limit to 200 - previously once could spit out 6.7MB - Username can be logged in with a serial number:
user_nameIt is also the credential for 'Login by username', and two accounts with the same name can cause someone to log into someone else's account (there are already 3 empty username history records online). Change to dual strategy: The background and AI create temporary names or report errors directly for duplicate names (changing names to others is also refused, excluding oneself); third-party automatic registration (nickname taken from the platform, users cannot change it) proceeduniqueUserNameAdd a numerical suffix, empty nickname degrades to 'User' and is truncated according to rune - refusing registration is equivalent to blocking third-party login.
Bug fixes
- Endpoint parameter schema may be cleared at one time.:Same as above. Added whitelist and two guards (one asserts the specific handler parses out the structure, one scans the controller source code, any call with the first parameter as ctx, name with payload//bind but not registered is reported directly as an error).
- Bulk update of anchor text almost all failed:
CurrentSiteThe returned is a copy of the request ctx, whilego currentSite.ReplaceAnchor(nil)This copy was taken into the goroutine, the request ctx was canceled immediately after the handler returned, and then every article returnedcontext canceled, the error was alsoReplaceContentDirectly swallow. Test failed 3557 articles out of 3557 (287ms 'completed'), showing 'clicked batch update, only a few articles changed'. New.'Website.Async()After removing the request ctx) 0 failures (3.065 seconds), and补充 'processing / failure / time' summary logs, task failure is no longer silent.' - Anchor text set in another three places 'set but no effect': After creating a new anchor text, clear the cache for 1 hour (add after warming up the cache and publishing again, no links in the text); the density limit is filled with the original text.
<a>Count of markdown link text, articles with more links are exhausted first, and the entire anchor text cannot be added; markdown text is replaced after thatdata-anchorRegular expression backfill, while markdown branches generate[词](链接)causing the 'replacement count' to be empty for a long time. In addition, the default density does not match the setting page and configuration comments (<20→200vs default 100), and batch replacement changes image addresses to local absolute addresses - Coupon batch code insertion error:
userId==0Branch is completely ignored in the loopCreateThe error, even if dozens of them all fail to be inserted, it still returns success, and the back-end receives the receipt and refreshes the list, but it is empty; the three branches of binding members also ignore the error, treating coupons that have never been written into the database as successfully claimed and returned to the user. Change to check one by one, go through the transaction in batches (either all fall into the database or none left, to avoid half-batch orphans causing administrators to retry and duplicate distribution when they try again),countLess than 1 is handled as 1. - Writing endpoint with empty payload, missing target, or non-existent target still returns success.51 endpoints still return 'Saved / Deleted' when the request body is empty, 24 return success when the id does not exist, and another 5 are consumed by the provider.
RowsAffected(gorm returns nil error when deleting a non-existent row). The input parameters have been unified to prevent errors. - Three handlers lack responses or miss returns.:
MakeCouponCodeThe successful path does not write the response body (the front-end pops up a blank success prompt, MCP side)endpointFailure("")Even failure is judged as success);SettingCacheFormofif req.UpdateThe branch does not write the response either;CommunicationOrderSaveImagesofReadJSONMissing return in the failure branch, done popping up.Invalid ParamsContinue to use zero value req to save. - The full-text search results requested in the
idsoverride:req.Idsjudgment is placed before the search results, with ids parameters (list page pagination, batch retrieval by id) Once the full-text search is started, the matched is the id list brought by the request itself, not the search results, and the sorting is also disrupted. - An upgrade appears to be a false success in the form of 'backup and backup again': The main program is renamed to
xxx.oldThe subsequent process still runs on the old inode, Linux will change/proc/self/exeto synchronizexxx.old(The backup is deleted and appended)(deleted)), while the old implementation directly takesos.Executable()When the main program is upgraded, and then click the upgrade again without restarting, it willxxx.oldBack up againxxx.old.old,the realxxxNot replaced once, the interface prompts success. NewNormalizeExecPathFold.old/.old.old/(deleted)Before upgrading, determine whether it is running on the backup program (if so, restart is required), if the main program fails to switch, it must return an error and roll back, front-endcode !== 0Explicitly prompt failure; if the main program is not found in the upgrade package, it will no longer be silent「Success」. ModuleId == 2Judging「whether it is a product」will cause silent misalignmentThe module ID is auto-incremented, after deleting the model and rebuilding or migrating the site, the ID may not be 2——the product page can still be opened, but the price block, SKU filtering, and Product structured data will all disappear without any error messages; conversely, renaming the 1st model to 'Product' does not take effect; in statistics,module_id = 2more directly makes the 'New Arrival' chart always 0. Change to readmodules.kind, and maintain this judgment cache separately (cannot reuse the one that only contains enabled items).- backend menu and dead routes: After moving the plugin menu to the third-level group, the dashboard guide, attachment replacement, material entry, keywords,留言邮件设置, etc. still point to the old flat path, and clicking leads to a blank page with no matching route;
google/llmsIn/plugina duplicate entry was left below. - Two routes at the mall endpoint have coverage defects:
googlemerchantofaccount/selectMissing the leading slash, actually registered as/plugin/googlemerchantaccount/select- The path can be seen in the list, but the real call will necessarily 404;coupon/detail/activity/detailThe 'save and fully override' protection of the 'Save as Full Coverage'补齐, to avoid only changing one field and clearing the rest of the configuration. - List failure results are cached, background tasks are queried repeatedly: Failed caching allows one fluctuation to take effect continuously, and background tasks repeatedly search the same batch of data each time they are scheduled.